array of objects selecting object based on attribute value - php

I am working with the Mailchimp API at the moment, I have a list of campaigns that have been run, or are due to be run, and I wanting to get the link for the most recently run campaign. How would I go about comparing the attribute "send_time" to find the most recent and it's attributed parent object?
The campaigns array looks like this,
{
"campaigns": [
{
"id": 1,
"type": "regular",
"status": "save",
"send_time": ""
},
{
"id": 2,
"type": "regular",
"status": "sent",
"send_time": "2015-11-11T14:42:58+00:00"
},
{
"id": 3,
"type": "regular",
"status": "sent",
"send_time": "2016-01-01T14:42:58+00:00"
},
{
"id": 4,
"type": "regular",
"status": "sent",
"send_time": "2016-06-12T14:42:58+00:00"
}
]
}
So in that above array, the final object has the most recent send_time, how would I assess this, and then grab that object? I have a semi solution, but it seems long winded.
<?php
//Build an array of send_times
$dates = [];
foreach($result['campaigns'] as $campaign) {
$dates[$campaign['id']] = $campaign['send_time'];
}
//Get the most recent date
$mostRecent = 0;
foreach($dates as $k => $v) {
$curDate = strtotime($v);
if($curDate > $mostRecent) {
$mostRecent = $curDate
$currentId = $k;
}
}
//Get the object
foreach($results['campaigns'] as $campaign) {
if($campaign['id'] == $currentId) {
$c = $campaign;
}
}
?>

Use array_multisort() like below (single line code):-
<?php
$data = '{
"campaigns": [
{
"id": 1,
"type": "regular",
"status": "save",
"send_time": ""
},
{
"id": 2,
"type": "regular",
"status": "sent",
"send_time": "2015-11-11T14:42:58+00:00"
},
{
"id": 3,
"type": "regular",
"status": "sent",
"send_time": "2016-01-01T14:42:58+00:00"
},
{
"id": 4,
"type": "regular",
"status": "sent",
"send_time": "2016-06-12T14:42:58+00:00"
}
]
}';
$array = json_decode($data,true)['campaigns']; // decode json string to array
array_multisort($array,SORT_DESC, SORT_STRING); // use of array_multisort
echo "<pre/>";print_r($array); // this will returns you indexed array like 0,1,2... you can again convert it to campaigns array like $final_array['campaigns'] = $array;
?>
Output:- https://eval.in/598349
Note:-
1.If your given data is in array already then no need to use json_decode(), directly use array_multisort() on it
https://eval.in/598355
For more reference:-
http://sg2.php.net/manual/en/function.array-multisort.php

<?php
$dates = [];
$recent_campaign = null;
$recent_time = 0;
foreach($result['campaigns'] as $campaign) {
$curDate = strtotime($campaign['send_time']);
if($curDate > $recent_time) {
$recent_time = $curDate
$recent_campaign = $campaign;
}
}
//$recent_campaign is the most recent campaign
?>
You can try this approach. Else you can use usort by send_time (direct solution).
I have not executed this code!

You can sort your objects by that property (I'd suggest usort, so that you can define your own sorting) and then get the first item in that array.
usort($result, function ($campaign1, $campaign2) {
if ($campaign1['send_time'] == $campaign2['send_time']) {
return 0;
}
return (strtotime($campaign1['send_time']) > strtotime($campaign2['send_time'])) ? -1 : 1;
});
$mostRecentCampaign = $campaign[0];
Note: I haven't run this, so you might have to tweak the return on the compare function if it's sorting in the wrong order.

If you just want to grab the max element and want to use fewer lines of code, try this:
Grab the max time by mapping the "send time" column to an epoch time and getting the max value
Filter your array by the entries that correspond to that max time
Profit
Example:
$dataArray = /* your array */
$maxTime = max(array_map('strtotime',array_column($dataArray["campaigns"], 'send_time')));
$maxEntry = array_filter($dataArray["campaigns"], function ($arr) use ($maxTime) { return strtotime($arr["send_time"])==$maxTime; });
print_r($maxEntry);
Would print:
Array
(
[3] => Array
(
[id] => 4
[type] => regular
[status] => sent
[send_time] => 2016-06-12T14:42:58+00:00
)
)
Note The advantage of this is that it doesn't need sorting. The disadvantage is that sorting and then getting the last element would be faster. However with sorting you lose the original array order which is sometimes needed.

Related

Refactor the JSON object in Laravel

Here's my JSON:
[
{
"ID": 1,
"SOURCEID": 1,
"TIMESTAMP": "2020-04-05 07:05:29",
"VALUE": "30"
},
{
"ID": 4,
"SOURCEID": 2,
"TIMESTAMP": "2020-04-05 07:05:17",
"VALUE": "40"
},
{
"ID": 3,
"SOURCEID": 1,
"TIMESTAMP": "2020-04-06 12:04:59",
"VALUE": "35"
},
{
"ID": 5,
"SOURCEID": 1,
"TIMESTAMP": "2020-06-17 12:01:32",
"VALUE": "1"
},
{
"ID": 6,
"SOURCEID": 2,
"TIMESTAMP": "2021-06-17 13:55:29",
"VALUE": "2"
}
]
I need to refactor the JSON like
I need JSON to be refactor based on timestamp and source id and JSON is dynamic like a number of source id present in the given JSON there are two ids that is 1 and 2. Below I gave the expected output.
I need a Unique time stamp in a separate array-like
[2020-04-05,2020-04-06,2020-06-17,2021-06-17]
{ "sourceid: 1, "data":[30,35,1,0], }, { "sourceid": 2, "data":[40,0,0,2], }
Note: The value fills according to the date. Other it should fill as 0.
I have tried like this :
`$data=json_decode($result);
$timestamp=[];
$dataList=[];
foreach ($data as $value){
$date=\Carbon\Carbon::parse($value->TIMESTAMP)->toDateString();
if(!in_array($date, $timestamp, true)){
array_push($timestamp, $date);
}
if(isset($dataList[$value->SOURCEID])){
array_push($dataList[$value->SOURCEID]['data'],$value->VALUE);
} else{
$dataList[$value->SOURCEID]=[
'SOURCEID'=>$value->SOURCEID,
'data'=>[$value->VALUE]
];
}
}
dump($timestamp);
dump($dataList);`
But it produce like
{ "sourceid: 1, "data":[30,35,1], }, { "sourceid": 2, "data":[40,2]}
but I need like
{ "sourceid: 1, "data":[30,35,1,0], }, { "sourceid": 2, "data":[40,0,0,2] }
You need to find the unique timestamps and source ids and then use that to develop your new array. Since you are using laravel, using a collection makes it easier.
I used the transform modifier to modify the TIMESTAMP field so we can "query" it easier via ->where
Please note, I am using the 2nd param true modifier for the json_decode function to get an associative array rather than an object.
$data = json_decode($result, true); // note true
$collection = collect($data);
// transform the timestamp column
$collection->transform(function ($item) {
$item['TIMESTAMP'] = \Carbon\Carbon::parse($item['TIMESTAMP'])->toDateString();
return $item;
});
// get all unique timestamps and source ids
$timestamps = $collection->pluck('TIMESTAMP')->unique();
$sourceIds = $collection->pluck('SOURCEID')->unique();
$dataList = [];
foreach ($sourceIds as $sourceId) {
$items = $collection->where('SOURCEID', $sourceId);
$dataList[$sourceId]['sourceid'] = $sourceId;
foreach ($timestamps as $timestamp) {
$occurrence = $items->where('TIMESTAMP', $timestamp)->first();
if ($occurrence) {
$dataList[$sourceId]['data'][] = $occurrence['VALUE'];
} else {
$dataList[$sourceId]['data'][] = 0;
}
}
}
dd($dataList);
Note you might want to cast $occurrence['VALUE']; to int via (int) $occurrence['VALUE'];
For more information on collections click here.
Output:

How to access sub array within array elements?

Here is a api response. What I am having problem is that there are several crew member here. I can just get anyone by their basic index. What do I do to get just the writers name or Should I say how can I access the array with Writer as parameter and get his/her name from this array.
"crew": [
{
"credit_id": "53f52157c3a36833fa002225",
"department": "Writing",
"gender": 2,
"id": 1930,
"job": "Writer",
"name": "Truman Capote",
"profile_path": "/kmhddndt7hI38aN6NzbhPcnjKSh.jpg"
},
{
"credit_id": "52fe4a6ec3a368484e152e99",
"department": "Directing",
"gender": 1,
"id": 96199,
"job": "Director",
"name": "Liz Garbus",
"profile_path": "/1Z5a6djeDX57yyLe2ixX3VzIaLJ.jpg"
},
{
"credit_id": "53f5216dc3a36833fd0021bd",
"department": "Writing",
"gender": 1,
"id": 96199,
"job": "Writer",
"name": "Liz Garbus",
"profile_path": "/1Z5a6djeDX57yyLe2ixX3VzIaLJ.jpg"
},
{
"credit_id": "53f52180c3a36834000023b2",
"department": "Writing",
"gender": 0,
"id": 1355119,
"job": "Writer",
"name": "Ralph Greenson",
"profile_path": null
}
]
Thanks in Advance for any help.
With PHP you can use FilterIterator classes like in the following example ...
class CrewFilter extends FilterIterator {
protected $department;
public function __construct(Iterator $iterator, $department) {
$this->department = $department;
parent::__construct($iterator);
}
public function accept(): bool
{
$current = $this->getInnerIterator()->current();
return $current->department === $department;
}
}
$data = json_decode($response);
$iterator = new ArrayIterator($data['crew']);
$filter = new CrewFilter($iterator, 'Writing');
foreach ($filter as $crew) {
// output the name
var_dump($crew->name);
}
What does the code above. The FilterIterator instance takes a $department parameter, that sets the department for the crew memebers you want to see. The first parameter is an instance of an Iterator. In this case this will be the json decoded data from the api. It 's elementary to decode the received json data.
The foreach loop will just give back the crew members with the department Writing.
if you want to search from browser side assume ur api response json is in obj here is the javascript code
var results = [];
var searchField = "job";
var searchVal = "Writer";
for (var i=0 ; i < obj.crew.length ; i++)
{
if (obj.list[i][searchField] == searchVal) {
results.push(obj.list[i]);
}
}
then the result variable will have all the writers data
hope this is the solution you are searching for
If you want to process JSON object in php then try following code
<?php
// JSON string
$someJSON = '[{"name":"Jonathan Suh","gender":"male"},{"name":"William Philbin","gender":"male"},{"name":"Allison McKinnery","gender":"female"}]';
// Convert JSON string to Array
$someArray = json_decode($someJSON, true);
print_r($someArray); // Dump all data of the Array
echo $someArray[0]["name"]; // Access Array data
// Convert JSON string to Object
$someObject = json_decode($someJSON);
print_r($someObject); // Dump all data of the Object
echo $someObject[0]->name; // Access Object data
?>
Or if you want to process the data in Javscript (client side)
obj = [{
"credit_id": "53f52157c3a36833fa002225",
"department": "Writing",
"gender": 2,
"id": 1930,
"job": "Writer",
"name": "Truman Capote",
"profile_path": "/kmhddndt7hI38aN6NzbhPcnjKSh.jpg"
},
{
"credit_id": "53f52157c3a36833fa002225",
"department": "Writing",
"gender": 2,
"id": 1930,
"job": "Writer",
"name": "Truman Capote 2",
"profile_path": "/kmhddndt7hI38aN6NzbhPcnjKSh.jpg"
},
]
obj.forEach(function(v,i){console.log(v.name)})
So based on everyone's suggestion I tried tweaking somethings though it's not fast but much effective.(PHP Code)
$data = json_decode($response,true);
$n = sizeof($data['crew']);
for ($i=0; $i < $n; $i++) {
if($data['crew'][$i]['job']=='Writer')
{
echo $data['crew'][$i]['name'];
}
}

array filter for array values

i have this code which does the job for searching option name how can i use it to search the option value from array.
$productspp ='[{
"id": 4674388066436,
"title": "1st march",
"options": [{
"id": 6046836162692,
"product_id": 4674388066436,
"name": "Size",
"position": 1,
"values": ["12", "24", "36"]
}, {
"id": 6067871875204,
"product_id": 4674388066436,
"name": "z",
"position": 2,
"values": ["blue", "green"]
}, {
"id": 6067871907972,
"product_id": 4674388066436,
"name": "Material",
"position": 3,
"values": ["silk", "cotton"]
}],
}, {
"id": 4674394325124,
"title": "2nd march",
"options": [{
"id": 6046844190852,
"product_id": 4674394325124,
"name": "Title",
"position": 1,
"values": ["Default Title"]
}],
}, {
"id": 4679851704452,
"title": "3rd marchhh",
"options": [{
"id": 6053112545412,
"product_id": 4679851704452,
"name": "Title",
"position": 1,
"values": ["Default Title"]
}]
}]';
$array = json_decode($productspp,1);
$filter_name555 ='options';
$dummytstt ='values';
$filter_value= blue;
$expected = array_filter($array, function($el) use ($filter_name555, $dummytstt, $filter_value) {
return ( stripos($el[$filter_name555][0][$dummytstt], $filter_value) !== false );
}
});
if the user searched option_value and it matches then it should list that product so in this case if user searches silk then it should list that product else not
for option name it works for option value it does not work as stripos expect it to be string but here in data it is array.
we tried in_array also to filter but that also did not work
when we search anything like 12 or 24 or 36 or blue or green then it should list this part of json. i mean this product and the code i have given above does the same but for option name. u can see that option value is array. it can have more than one values so my code is failing.
{
"id": 4674388066436,
"title": "1st march",
"options": [{
"id": 6046836162692,
"product_id": 4674388066436,
"name": "Size",
"position": 1,
"values": ["12", "24", "36"]
}, {
"id": 6067871875204,
"product_id": 4674388066436,
"name": "z",
"position": 2,
"values": ["blue", "green"]
}, {
"id": 6067871907972,
"product_id": 4674388066436,
"name": "Material",
"position": 3,
"values": ["silk", "cotton"]
}],
}
You need to distinguish between an array value or regular value, because they need to be matched differently.
One thing you could do is write logic for if a value is an array, and then force any other kind of value into an array of just one element.
$key = 'options';
$attr = 'values';
$search = 'blue';
$expected = array_filter($array, function($el) use ($key, $attr, $search) {
$values = $el[$key];
if (is_array($value)) {
$values = array_column($value, $attr);
} else {
$value = array($value);
}
foreach ($value as $body) {
foreach ((array)$body as $contents) {
if (stripos($contents, $search) !== false) {
return true;
}
}
}
return false;
}
Your basic problem is that you have a multidimensional array and you're not iterating on all the levels. To make this work, I had to rework the entire logic, but I did in hope this will be a good learning exercise.
The array_filter function only works with the given level. You could keep it, but I'll propose a solution that merely uses nested loops:
/**
* Selects products where options match the searched one by removing the ones that don't match.
*/
function selectProductsWithMatchingOptions(array $products, string $filterName, string $filterValue): array
{
foreach ($products as $key => $product) {
if (!hasMatchingOption($product['options'], $filterName, $filterValue)) {
unset($products[$key]);
}
}
return $products;
}
/**
* Checks whether the searched filter is within any of the options for the product.
*/
function hasMatchingOption(array $options, string $filterName, string $filterValue): bool
{
foreach ($options as $option) {
// this part takes care of "values", which is an array
if (is_array($option[$filterName]) && valueIsListed($option[$filterName], $filterValue)) {
return true;
// this part takes care of "name", which is a string
} elseif (is_string($option[$filterName]) && stringMatches($filterValue, $option[$filterName])) {
return true;
}
}
return false;
}
/**
* Checks if a value is in the list of values.
*/
function valueIsListed(array $values, string $filterValue): bool
{
// we have to iterate and check with stripos because in_array can't handle case
foreach ($values as $value) {
if (stringMatches($filterValue, $value)) {
return true;
}
}
return false;
}
function stringMatches(string $needle, string $haystack): bool
{
return stripos($needle, $haystack) !== false;
}
Now if you test this with your array:
$filtered = selectProductsWithMatchingOptions($array, 'name', 'mAterial');
or
$filtered = selectProductsWithMatchingOptions($array, 'values', 'BLUE');
dumping $filtered should show desired results.
I've made a couple of functions because I prefer extracting smaller pieces of logic. It makes for cleaner code. The alternative would be storing boolean values in variables to know whether we've found a match or not and that tends to lower readability. Plus, this way we can abandon the loop more elegantly, by returning true as soon as we find a match. That's nicer than having to break, potentially through multiple levels.
Note that I haven't passed the 'options' key as a parameter, because it's the only one that can be iterated - this function won't work for 'id' or 'title'. It can be modified to handle those too, but I'll leave that up to you.
Also note that this functionality would still work even if the number of options changes (you mentioned in the comment that max is 3), because it is agnostic of the number of elements. Always aim for more general solutions whenever it doesn't require too much effort. You'll thank yourself later.

PHP merge two arrays with same key and output to specific json format

My database has two tables. One is calling out the book info and another is calling image info. I would like to merge these two table data into one JSON. If the img id is matched with the data id, then the image is belongs to this book. I tried to use foreach to loop the book data into array and use another foreach to loop the image data within the book data array, but failed to get the expected result.
Book Table JSON:
{
"data": [
{
"id": 17,
"author": "Belcurls",
"bookname": "You Never Know"
},
{
"id": 18,
"author": "Carolina",
"bookname": "A Story Teller"
},
{
"id": 19,
"author": "Lokas",
"bookname": "The Love"
}
]
}
Image Table JSON:
{
"img": [
{
"id": 18,
"url": "image18.png"
},
{
"id": 18,
"url": "image18b.png"
},
{
"id": 19,
"url": "image19.png"
},
{
"id": 19,
"url": "image19b.png"
},
{
"id": 19,
"url": "image19c.png"
}
]
}
Expected Result:
{
"data": [
{
"id": 17,
"author": "Belcurls",
"bookname": "You Never Know"
},
{
"id": 18,
"author": "Carolina",
"bookname": "A Story Teller",
"image":[
{
"url":"image18"
},
{
"url":"image18b"
}
]
},
{
"id": 19,
"author": "Lokas",
"bookname": "The Love",
"image":[
{
"url":"image19"
},
{
"url":"image19b"
},
{
"url":"image19c"
}
]
}
]
}
Demo Link.
You can do this loop, Please check inline doc for explanation
foreach ($arr['data'] as $key => &$value) { // & to update changes as its address
foreach ($imgs['img'] as $key1 => $value1) {
if($value['id'] == $value1['id']){ // checking if match id of parent with images
$value['image'][] = ['url' => $value1['url']]; // then simply push
}
}
}
If you want to convert your json to php array use
json_decode($yourjson, true); // second parameter is for converting it to array else it will convert into object.
If you make the data array associative then you only need to loop the image array and add them to the correct subarray in data.
// This flattens the array and makes it associative
$data = array_column($data['data'], null, 'id');
foreach($img['img'] as $v){
$data[$v['id']]['image'][] = ['url' => $v['url']];
}
// Add the 'data' again
$final['data'] = array_values($data);
var_dump($final);
echo json_encode($final);
https://3v4l.org/736lQ

JSON structure PHP - strange

Im working with Google tag Manager API(v2), and trying to add some new lines to my existing JSON array. I first had some trouble adding data to my JSON object because the data was shown on the last row and not inside the object itself.
Now ive changed my code but the data does not look like its inside the object itself but im a little bit closer now. This is how my JSON object looks like now:
JSON:
{
"Account2": [{
"accountId": "17467*****",
"containerId": "745****",
}, 3, 3, 4, {
"accountId": "17467*****",
"containerId": "751****",
}, 1, 1, 2],
"Account 1": [{
"accountId": "17661*****",
"containerId": "748****",
}, 2, 1, 1],
"GTMdocx": [{
"accountId": "21200*****",
"containerId": "803****",
}, 0, 0, 1],
"Account3": [{
"accountId": "21281*****",
"containerId": "803****",
}, 0, 0, 0]
}
As you can see the object is structured like this:
1) Accountname
2) Inside Accountname we have accountId and containerId
3) at last of one Accountname object we have some numbers(count), the problem is that it looks like this:
What i would want the JSON-object to look like is like this:
{
"Account2": [{
"accountId": "17467*****",
"containerId": "745****",
"tags": "3",
"triggers": "3",
"variables": "4"
}, {
"accountId": "17467*****",
"containerId": "751****",
"tags": "3",
"triggers": "3",
"variables": "4"
}],
"Account 1": [{
"accountId": "17661*****",
"containerId": "748****",
"tags": "2",
"triggers": "1",
"variables": "1"
}],
"GTMdocx": [{
"accountId": "21200*****",
"containerId": "803****",
"tags": "0",
"triggers": "0",
"variables": "1"
}],
"Account3": [{
"accountId": "21281*****",
"containerId": "803****",
"tags": "0",
"triggers": "0",
"variables": "0"
}]
}
You can see the numbers got a key and then value inside the object.
This is my PHP-code:
static public function listAllContainers() {
try { //Because some containers might not have live-version
$containers[] = array();
foreach (self::listAccounts()->account as $accountKey => $account) {
foreach (self::listAccountsContainers($account["path"]) as $account_container) {
$containers[$account['name']][] = $account_container;
$container_version = self::listAccountsContainersVersion($account_container['path']);
$containers[$account['name']][] = count($container_version['tag']);
$containers[$account['name']][] = count($container_version['trigger']);
$containers[$account['name']][] = count($container_version['variable']);
}
}
$filtered_array = (object)array_filter((array)$containers); // Removes empty object
return $filtered_array;
} catch (\Exception $e) {
return $e->getCode();
}
}
Hope you understand my problem. Not so much experience with Arrays/JSON so maybe my explanation is not so good.
Thanks!
UPDATE:
Using $containers[$account['name']]['tag'] = count($container_version['tag']); gives me this JSON: (PS: ive removed some from the json so it wont be long, this makes the json invalid)
{
Account2: {
0: {
accountId: "174675****",
containerId: "745****",
},
1: {
accountId: "174675****59",
containerId: "751****83",
},
tag: 1,
trigger: 1,
variable: 2
},
Account 1: {
0: {
accountId: "1766***525",
containerId: "748***53",
},
tag: 2,
trigger: 1,
variable: 1
},
Now each "Account" can have multiple "containers"(accountId,containerId), using this method does not store the data inside the object itself.
If you have an array, you can add a key:value pair to it with the following syntax:
$array['key'] = 'value';
//array('key' : 'value')
If you don't specify a key, it will just add the value:
$array[] = 'value';
//array('value')
The last part is what you are doing now, but you add it to your $containers[...] array. What you want to do is add the values first to your $account_container array, and then add that array to your $containers[...] array. Put this in the inner foreach:
$container_version = self::listAccountsContainersVersion($account_container['path']);
//Add values to account_container
$account_container['tag'] = count($container_version['tag']);
$account_container['trigger'] = count($container_version['trigger']);
$account_container['variable'] = count($container_version['variable']);
// Add account_container to your $containers[..] array
$containers[$account['name']][] = $account_container;
I put together another solution without modifying the original $account_container.
I've added the new data in a new array $account_container_extra and merged that together with $account_container when appending it to the $container array.
Maybe that will work better for you or give you some other ideas.
static public function listAllContainers() {
try { //Because some containers might not have live-version
$containers[] = array();
foreach (self::listAccounts()->account as $accountKey => $account) {
foreach (self::listAccountsContainers($account["path"]) as $account_container) {
$container_version = self::listAccountsContainersVersion($account_container['path']);
$account_container_extra = [
'tag' => count($container_version['tag']),
'trigger' => count($container_version['trigger']),
'variable' => count($container_version['variable'])
];
$containers[$account['name']][] = array_merge($account_container, $account_container_extra);
}
}
$filtered_array = (object)array_filter((array)$containers); // Removes empty object
return $filtered_array;
} catch (\Exception $e) {
return $e->getCode();
}
}

Categories