I need to parse JSON which looks like this:
{
"mdfId":"282088127",
"mdfConcept":"ME 3400EG-12CS-M Switch",
"children":[
{
"mdfId":"007",
"mdfConcept":"Another item",
"children": [
// many more here
]
},
{
"mdfId":"008",
"mdfConcept":"Another one",
"children": [
{
"mdfId":"010",
"mdfConcept":"What I'm looking for!",
"children": [] // no children
}
]
},
// many more here
]
},
This is a recursive structure in which every element has mdfId, mdfConcept and children keys.
Say I need to find node with ID=010 within this structure. I don't know at which level it lies (e.g. it can be on top level, or several children nodes below).
My current approach is:
$mdfId = '010'; // what I'm loking for
foreach ($jsonResponse as $category) {
while (true) {
if ($category['mdfId'] == $mdfId) {
// we found it!
$categoryDevices[$mdfId] = $category['children'];
break 2;
}
if (!empty($category['children'])) {
next_cat:
if (is_null($category['children'])) {
break;
}
$category = array_shift($category['children']);
continue;
}
if (empty($category['children'])) {
goto next_cat;
}
}
}
But current approach misses some cases. How can I optimize this recursive loop so it checks all nodes on same level and each one accesible through any number of children keys?
An embarrassing feature of your JSON object is that, while each children member is an array of the "child" structure, the top level one is the object itself, so it's an obstacle to a really recursive approach.
We might workaround by turning the source JSON object into the same structure as nested levels, i.e.:
having $jsonResponse as original object
use ['children' => $jsonResponse] instead
This way, it should work with something like this:
$mdfId = '010'; // what I'm loking for
if ($result = look4id(['children' => $jsonResponse], $mdfId) {
$categoryDevices[$mdfId] = $result;
}
function look4id($source, $id) {
foreach ($source as $child) {
if ($child['mdfId'] == $id) {
return $source['children'];
} else {
if ($source['children']) {
return look4id($source['children'], $id);
}
}
}
}
So basically I wrote a function that didn't return anything, but rather populated a variable from arguments.
function findRecursiveArrayNodeById($id, $array, &$node) {
foreach ($array as $child) {
if (isset($child['mdfId']) && $child['mdfId'] == $id) {
$node = $child;
return;
}
if (!empty($child['children'])) {
findRecursiveArrayNodeById($id, $child['children'], $node);
}
}
}
Usage as follows:
$result = false;
findRecursiveArrayNodeById($mdfId, $category_json, $result);
if (!$result) {
println("did not find {$mdfId}");
continue;
}
Related
I have a very large object that I have to loop through to get a specific object.
I tried foreach($obj as $item), foreach($obj as $item => $value) and for in various configurations.
I want to get all the objects where the class is "table-responsive"
My Element looks like this (in JSON):
{
"dummy":"dummytext",
"children": [
{
"tag":null
},
{
"tag":"body",
"children": [
{
"class":"not_the_one"
},
{
"class":"table-responsive",
"html":"Gotcha!"
}
]
}
]
}
What I want to get as a result is:
{
"class":"table-responsive",
"html":"Gotcha!"
}
There could be more than one object that has the class of "table-responsive", so if there are multiple ones:
[
{
"class":"table-responsive",
"html":"Gotcha!"
},
{
"class":"table-responsive",
"html":"Gotcha!"
}
]
I was trying to accomplish a function that goes through all the elements and checks where the key of class equals "table-responsive" if so, push the object to an array. (array_push($result, $obj);) If the current element is an array, loop through it too. There can be a lot of dimensions so ideally the function calls itself. (recursive)
What you're after is a recursive reduce operation. Something that can
Iterate your elements
Capture any with the matching property, and
Perform the same operation on any children
// decode your JSON into an associative array
$data = json_decode($json, true);
// setup search parameters
$findKey = 'class';
$findValue = 'table-responsive';
// create a recursive reducer
$reducer = function($carry, $item) use (&$reducer, $findKey, $findValue) {
if (array_key_exists($findKey, $item) && $item[$findKey] === $findValue) {
// if this $item matches, add it to the $carry array
$carry[] = $item;
}
if (!empty($item['children'])) {
// this $item has children so dive deeper
return array_reduce($item['children'], $reducer, $carry);
}
// no children in this one so on to the next iteration
return $carry;
};
// Run the reducer at the top level.
// Note I've wrapped the root object in an array so the reducer can iterate it
$found = array_reduce([ $data ], $reducer, []);
// and display
echo json_encode($found, JSON_PRETTY_PRINT);
Demo ~ https://3v4l.org/6mWmC
I need to find the direct parent of all instance of "type": "featured-product" in a JSON file using PHP. store this parent string in a variable. use a foreach.
In the example below, the variable would have the value "1561093167965" and "3465786822452"
I'm a little lost, thank you for the help!
{
"current": {
"sections": {
"1561093167965": {
"type": "featured-product"
},
"3465786822452": {
"type": "featured-product"
}
}
}
}
foreach ($json['current']['sections'] as $sectionName => $section) {
if ($section['type'] && $section['type'] == 'featured-product') {
$featuredId = $sectionName;
}
}
Another approach you can take is to create a new array containing only featured-products using array_filter and then extract the keys. From the docs:
If the callback function returns TRUE, the current value from array is
returned into the result array. Array keys are preserved.
$product_sections = array_keys(
array_filter($json['current']['sections'], function($val) {
return $val['type'] === 'featured-product';
}));
Demo
The problem in your original code is that your $featuredId variable is getting overwritten in each iteration of the loop, so when it ends its value will be the one of last element processed. If you have to deal with multiple values, you'll have to add it to an array or do the work directly inside the foreach. You can see the other answers for how to fix your code.
There is probably a cleaner way but this works using json_decode and iterating over the array with foreach
$json='{
"current": {
"sections": {
"1561093167965": {
"type": "featured-product"
},
"3465786822452": {
"type": "featured-product"
}
}
}
}';
$e=json_decode($json,true);
foreach($e['current']['sections'] as $id=>$a){
if($a['type']=='featured-product'){
echo 'the parent id is '.$id;
}
}
//change this with the real json
$json='{
"current": {
"sections": {
"1561093167965": {
"type": "featured-product"
},
"3465786822452": {
"type": "featured-product"
}
}
}
}';
$result = [];
$jsond=json_decode($json,true);
foreach($jsond['current']['sections'] as $k=>$v){
if($v['type']=='featured-product'){
$result[] = $k;
}
}
I have this problem where an API responds to me with DEPARTURESEGMENT sometimes containing only one object, and sometimes containing an array of objects. Depending on which case it is, I seem to need different logics in my foreach-loop.
Response A:
{
"getdeparturesresult":{
"departuresegment":[{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
},
{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
}
]
}
}
Works with this loop:
foreach ($apiData->getdeparturesresult->departuresegment as $m) {
While this response B:
{
"getdeparturesresult":{
"departuresegment":{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
}
}
}
needs a loop like this (otherwise it throws an error):
foreach ($apiData->getdeparturesresult as $m) {
Is there a way to write the loop failsafe for whether DEPARTURESEGMENT is an array of objects or just one object (the brackets [] is the only difference to the structure of the json right?) or do I have to somehow test and see first whether DEPARTURESEGMENT is an array or not, and dispatch to two different loops depending on the outcome?
You have a few methods that can help you:
is_array
is_object
instanceof // if you receive specific object
gettype
json_decode second parameter, which if is set to true, tries to decode the json as an array
In you situation, you would probably be fine by doing the following:
if (is_object($entry)) {
handleObject($entry);
} elseif (is_array($entry) && count($entry)) {
foreach ($entry as $e) {
handleObject($e);
}
}
I have this little useful function in my standard repertoire:
function iter($x) {
if(is_array($x))
return $x;
if(is_object($x)) {
if($x instanceof \Iterator)
return $x;
if(method_exists($x, 'getIterator'))
return $x->getIterator();
return get_object_vars($x);
}
return array($x);
}
This way you can use any variable with foreach without having to check it beforehand:
foreach(iter($whatever) as $item)
...
How about checking whether it's an array or not with is_array?
I made a simple example of it's usage here - http://codepad.org/WNjbIPZF
In a multidimensional array, I'm trying to select all descendant arrays with a certain key, no matter what their parent arrays are. I know the following syntax doesn't work, but hopefully it will help illustrate what I'm trying to accomplish:
<?php
foreach ($array[*][*]['descendant'] as $descendent) {
// do stuff
}
?>
Similarly, I need to figure out whether sibling arrays do not contain this array key. Something like this (again, I know the syntax is horribly wrong):
<?php
foreach ($array[*][*]['descendant'] < 1 as $descendent) {
// do stuff
}
?>
If there are always 3-dimensional array, you can use nested loop:
foreach($array as $lv1) {
foreach($lv1 as $lv2) {
foreach($lv2['descendant'] as $descendent) {
// do stuff
}
}
}
If you want to support unlimited number of dimension, you can use this ugly code
function drill($arr) {
if (isset($arr) && is_array($arr)) {
foreach($arr as $key => $value) {
if ($key == 'descendant') {
foreach($value as $descendent) {
// do stuff here
}
} else {
drill($value);
}
}
}
}
drill($array);
I currently have some code which grabs some JSON from a site. This is basically what I currently do
$valueObject = array();
if (isset($decoded_json->NewDataSet)) {
foreach ($decoded_json->NewDataSet->Deeper as $state) {
$i = count($valueObject);
$valueObject[$i] = new ValueObject();
$valueObject[$i]->a = $state->a;
}
Now the problem occurs when there is only one 'Deeper'. The server returns it as a JSON object. $state then becomes each key in the Deeper object. $state->a won't exist until around position 7 for example. Is there any way I could convert Deeper from a JSON object to array when the count of deeper is one?
Hopefully this helps illustrate my problem:
"NewDataSet": {
"Deeper": [
{
"a": "112",
"b": "1841"
},
{
"a": "111",
"b": "1141"
}
]
}
}
versus
"NewDataSet": {
"Deeper":
{
"a": "51",
"b": "12"
}
}
converting above to
"NewDataSet": {
"Deeper": [
{
"a": "51",
"b": "12"
}
]
}
would be great. I don't know how to do this
Before
foreach ($decoded_json->NewDataSet->Deeper as $state)
you probably want to:
if (is_array($decoded_json->NewDataSet)) {
// This is when Deeper is a JSON array.
foreach ($decoded_json->NewDataSet->Deeper as $state) {
// ...
}
} else {
// This is when Deeper is a JSON object.
}
Update
If you just want to make $decoded_json->NewDataSet->Deeper into an array, then:
if (!is_array($decoded_json->NewDataSet->Deeper)) {
$decoded_json->NewDataSet->Deeper = array($decoded_json->NewDataSet->Deeper);
}
foreach ($decoded_json->NewDataSet->Deeper as $state) {
// ...
}