I have a multi- and variable-level json file that looks like the example at the bottom of this post. What I need to do is search for a particular value, let's say "Gap junction" and return the value for "name" for all of the higher level parents, in this example "Junction", "Plasma membrane" and "Cell". I need to do this using php and I think I need a recursive loop to traverse the array that will record the "name" value for each depth level into an array and then return this "name" array once the search term is found, but I'm struggling a bit at achieving this. Any help would be appreciated.
{
"name": "Cell",
"children": [
{
"name": "Plasma membrane",
"children": [
{
"name": "Junction",
"children": [
{"name": "Adherens junction"},
{"name": "Caveola"},
{"name": "Gap junction"},
{"name": "Lipid raft"},
{"name": "Tight junction"}
]
},
{"name": "Focal adhesion"}
]
},
{
"name": "Vesicle",
"children": [
{
"name": "Endosome",
"children": [
{"name": "Early Endosome"},
{"name": "Late Endosome"},
{"name": "Recyling Endosome"}
]
},
{ "name": "Microsome"}
]
}
]
}
EDIT
Current code as requested. The $found variable is certainly not working how I intended it too. Code is based/modified on this answer: Get Parent and Child depth level from JSON using PHP?
$jsonString = file_get_contents("./information/localization.json");
$jsonArray = json_decode($jsonString);
$currOrganelle = "Gap junction";
$parents = read_tree_recursively($jsonArray, $currOrganelle);
function read_tree_recursively($items, $searchTerm, $result = array(), $level = 0, $found = false) {
foreach($items as $child) {
if(!$found) {
$currName = $child->name;
if($currName == $searchTerm) {
$found = true;
return $result;
}
elseif(!empty($child->children)) {
$result[$level] = $currName;
$result = read_tree_recursively($child->children, $searchTerm, $result, $level + 1, $found);
if($found) return $result;
}
else {
}
}
else {
return $result;
}
}
}
Solution with RecursiveIteratorIterator and RecursiveArrayIterator classes:
// $str - is your initial json string
$decoded = json_decode($str, TRUE);
function getParentNameKeys($arr = [], $needle = "") {
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($arr));
$nameKeys = [];
foreach ($iterator as $key => $value) {
if ($value === $needle) {
$depth = $iterator->getDepth();
while ($depth--){
if ($iterator->getSubIterator($depth)->offsetExists('name')) {
$nameKeys[] = $iterator->getSubIterator($depth)->offsetGet('name');
}
}
}
}
return $nameKeys;
}
$nameKeys = getParentNameKeys($decoded, "Gap junction");
var_dump($nameKeys);
// the output:
array (size=3)
0 => string 'Junction' (length=8)
1 => string 'Plasma membrane' (length=15)
2 => string 'Cell' (length=4)
$nameKeys = getParentNameKeys($decoded, "Early Endosome");
var_dump($nameKeys);
// the output:
array (size=3)
0 => string 'Endosome' (length=8)
1 => string 'Vesicle' (length=7)
2 => string 'Cell' (length=4)
http://php.net/manual/en/class.recursiveiteratoriterator.php
Related
This question already has answers here:
How to array_merge_recursive() an array?
(2 answers)
Closed 3 months ago.
I have an array like this:
[
{
"function_1": {
"element": {
"error": "0",
"msg": "test"
}
}
},
{
"function_1": {
"element_2": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element_2": {
"error": "0",
"msg": "test"
}
}
}
]
I want output like this:
[
{
"function_1": {
"element": {
"error": "0",
"msg": "test"
},
"element_2": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element": {
"error": "0",
"msg": "test"
},
"element_2": {
"error": "0",
"msg": "test"
}
}
}
]
The answers that I found offered to search by name("function_1", "function_2"). But this does not suit me, the function will not always pass an array. I need exactly the "depth" or any other reasonable way.
Thank you!
To achieve your desired result, you could json-decode, recursively merge each individual subarray, then loop over that structure to push each item as a second-level array like this: (Demo)
$array = json_decode($json, true);
$merged = array_merge_recursive(...$array);
$result = [];
foreach ($merged as $key => $data) {
$result[] = [$key => $data];
}
var_export($result);
But I can't imagine getting any benefit from adding unnecessary depth to your result array. I recommend simply json decoding, then calling array_merge_recursive() with the spread operator: (Demo)
var_export(
array_merge_recursive(
...json_decode($json, true)
)
);
Output:
array (
'function_1' =>
array (
'element' =>
array (
'error' => '0',
'msg' => 'test',
),
'element_2' =>
array (
'error' => '0',
'msg' => 'test',
),
),
'function_2' =>
array (
'element' =>
array (
'error' => '0',
'msg' => 'test',
),
'element_2' =>
array (
'error' => '0',
'msg' => 'test',
),
),
)
Your data structure looks weird for the purpose you are trying to achieve I'm bored af tho and created this code for you
function combineElementsPerfunction($functions) {
$result = [];
$uniqueFunctions = [];
foreach ($functions as $function) {
$functionName = array_keys($function)[0];
$uniqueFunctions[] = $functionName;
}
$uniqueFunctions = array_unique($uniqueFunctions);
foreach ($uniqueFunctions as $uniqueFunction) {
$functionObjects = array_filter(
$functions,
function($function) use ($uniqueFunction) {
$functionName = array_keys($function)[0];
return $functionName === $uniqueFunction;
}
);
$elements = [];
foreach ($functionObjects as $functionObject) {
$function = array_shift($functionObject);
$elements = array_merge($elements, $function);
}
$result[] = [
$uniqueFunction => $elements
];
}
return $result;
}
function changeArr($data){
$box = $new = [];
foreach ($data as $v){
$key = array_key_first($v);
$i = count($box);
if(in_array($key, $box)){
$keys = array_flip($box);
$i = $keys[$key];
}else{
$box[] = $key;
}
$new[$i][$key] = isset($new[$i][$key]) ? array_merge($new[$i][$key], $v[$key]) : $v[$key];
}
return $new;
}
This question already has answers here:
How to GROUP BY and SUM PHP Array? [duplicate]
(2 answers)
Closed 9 months ago.
I'm having a hard time manipulating an array of objects in PHP. I need to group the objects by id, while summing up the points.
Starting array of objects:
[
{
"id": "xx",
"points": 25
},
{
"id": "xx",
"points": 40
},
{
"id": "xy",
"points": 40
},
]
What I need:
[
{
"id": "xx",
"points": 65
},
{
"id": "xy",
"points": 40
},
]
As a frontender, I'm having a hard time with object/array manipulations in PHP. Any help would be greatly appreciated!
i hope this answer help you
first i will change objects to array and return the result to array again
$values =[
[
"id"=> "xx",
"points"=> 25
],
[
"id"=> "xx",
"points"=> 40
],
[
"id"=> "xy",
"points"=> 40
],
];
$res = array();
foreach($values as $vals){
if(array_key_exists($vals['id'],$res)){
$res[$vals['id']]['points'] += $vals['points'];
$res[$vals['id']]['id'] = $vals['id'];
}
else{
$res[$vals['id']] = $vals;
}
}
$result = array();
foreach ($res as $item){
$result[] = (object) $item;
}
output enter image description here
Parse JSON as Object
Aggregate Data
Put back as JSON
$json = <<<'_JSON'
[
{
"id": "xx",
"points": 25
},
{
"id": "xx",
"points": 40
},
{
"id": "xy",
"points": 40
}
]
_JSON;
$aggregate = [];
foreach(json_decode($json) as $data) {
if(!isset($aggregate[$data->id])) $aggregate[$data->id] = 0;
$aggregate[$data->id] += $data->points;
}
$output = [];
foreach($aggregate as $id => $points) {
$output[] = ['id' => $id, 'points' => $points];
}
echo json_encode($output);
[{"id":"xx","points":65},{"id":"xy","points":40}]
You may use array_reduce buil-in function to do the job. Also, when looping through the object's array (the callback), you should check if the result array has the current item's ID to verify that wether you need to add the item to the result array or to make the sum of points attributes.
Here's an example:
// a dummy class just to replicate the objects with ID and points attributes
class Dummy
{
public $id;
public $points;
public function __construct($id, $points)
{
$this->id = $id;
$this->points = $points;
}
}
// the array of objects
$arr = [new Dummy('xx', 25), new Dummy('xx', 40), new Dummy('xy', 40)];
// loop through the array
$res = array_reduce($arr, function($carry, $item) {
// holds the index of the object that has the same ID on the resulting array, if it stays NULL means it should add $item to the result array, otherwise calculate the sum of points attributes
$idx = null;
// trying to find the object that has the same id as the current item
foreach($carry as $k => $v)
if($v->id == $item->id) {
$idx = $k;
break;
}
// if nothing found, add $item to the result array, otherwise sum the points attributes
$idx === null ? $carry[] = $item:$carry[$idx]->points += $item->points;
// return the result array for the next iteration
return $carry;
}, []);
This will result in something like this:
array(2) {
[0]=>
object(Dummy)#1 (2) {
["id"]=>
string(2) "xx"
["points"]=>
int(65)
}
[1]=>
object(Dummy)#3 (2) {
["id"]=>
string(2) "xy"
["points"]=>
int(40)
}
}
Hope that helps, feel free to ask for further help.
Let's use a helper variable called $map:
$map = [];
Build your map:
foreach ($input => $item) {
if (!isset($map[$item["id"]])) $map[$item["id"]] = 0;
$map[$item["id"]] += $item["points"];
}
Now let's build the output:
$output = [];
foreach ($map as $key => $value) {
$output[] = (object)["id" => $key, "points" => $value];
}
[
{
"id": 1573695284631,
"name": "Cars",
"pid": 0,
"children": [
{
"id": 1573695292010,
"name": "Audi",
"pid": 1573695284631
},
{
"id": 1573695305619,
"name": "BMW",
"pid": 1573695284631,
"children": [
{
"id": 1573695328137,
"name": "3 Series",
"pid": 1573695305619
},
{
"id": 1573695335102,
"name": "X5",
"pid": 1573695305619
}
]
}
]
},
{
"id": 1573695348647,
"name": "Motorcycles",
"pid": 0,
"children": [
{
"id": 1573695355619,
"name": "Ducatti",
"pid": 1573695348647
}
]
}
]
Suppose I have this node-tree-like array in PHP (represented above in json for readability). For a given child node ID, I would like to find all parent node IDs that it's nested under. For example,
getParentNodes($haystack, $child_node_id=1573695328137); //[1573695284631, 1573695292010, 1573695305619]
I assume this is a use case for recursion. Here's my best attempt:
function getParentNodes($haystack, $child_node_id) {
if( empty($haystack->children) )
return;
foreach($haystack->children as $child) {
if($child->id == $child_node_id) {
// $child found, now recursively get parents
} else {
getParentNodes($child, $child_node_id);
}
}
}
This one will walk the tree until it hits the desired id.
In all cases where the leaf is not the desired one, it will return false - and collapse up the stack resulting in false or an array of parent-ids if the child is found.
Code
function getParentNodes($haystack, $child_node_id) {
foreach ($haystack as $element) {
if ($element['id'] === $child_node_id) {
// return [$element['id']]; // uncomment if you want to include child itself
return [];
} else if (array_key_exists('children', $element)) {
$parentNodes = getParentNodes($element['children'], $child_node_id);
if ($parentNodes !== false) {
return [$element['id'], ...$parentNodes];
}
}
}
return false;
}
Outputs parent ids:
array(2) {
[0]=>
int(1573695284631)
[1]=>
int(1573695305619)
}
Working example.
You missing return result. This is what you want.
function getParentNodes($arr, $child_node_id) {
$result = [];
foreach($arr as $item) {
if($item->pid == $child_node_id) {
$result[] = $item->id;
}
if(!empty($item->children)) {
$result[] = getParentNodes($item->children, $child_node_id);
}
}
return $result;
}
also you need get values as flat array
$values = getParentNodes($values, 1573695284631);
// do flat arr
array_walk_recursive($values,function($v) use (&$result){ $result[] = $v; });
// your values
var_dump($result);
Source reference for flat array
I ended up writing 2 recursive functions
function treeSearch($needle, $haystack) {
foreach($haystack as $node) {
if($node->id == $needle) {
return $node;
} elseif ( isset($node->children) ) {
$result = treeSearch($needle, $node->children);
if ($result !== false){
return $result;
}
}
}
return false;
}
treeSearch will find the node in the tree, then I need to recursively go up the tree until the parent id (pid) is 0
function getParents($node, $hierarchy, $all_parent_ids=[]) {
if($node->pid != 0) {
$parent_node = treeSearch($node->pid, $hierarchy);
$all_parent_ids[] = $parent_node->id;
$result = getParents($parent_node, $hierarchy, $all_parent_ids);
if ($result !== false){
return $result;
}
}
return $all_parent_ids;
}
then, supposing the tree is called $tree I can call them like:
$node = treeSearch(1573695328137, $tree);
$parents = getParents($node, $tree);
This is the best solution to take the parent of a child !
function getPathParent($id, $tree='',$opts='', &$path = array()) {
$in = array_replace(array(
'id'=>'id',
'child'=>'children',
'return'=>'id'
),(array)$opts);
if ( is_array($tree) && !empty($tree) ){
foreach ($tree as $item) {
if ($item[$in['id']] == $id) {
array_push($path, $item[$in['return']]);
return $path;
}
if ( isset($item[$in['child']]) && !empty($item[$in['child']]) ) {
array_push($path, $item[$in['return']]);
if (getPathParent($id, $item[$in['child']],$opts, $path) === false) {
array_pop($path);
} else {
return $path;
}
}
}
}
return false;
}
$tree = [
[
"id" => 1573695284631,
"name" => "Cars",
"pid" => 0,
"children" => [
[
"id" => 1573695292010,
"name" => "Audi",
"pid" => 1573695284631
],
[
"id" => 1573695305619,
"name" => "BMW",
"pid" => 1573695284631,
"children" => [
[
"id" => 1573695328137,
"name" => "3 Series",
"pid" => 1573695305619
],
[
"id" => 1573695335102,
"name" => "X5",
"pid" => 1573695305619
]
]
]
]
],
[
"id" => 1573695348647,
"name" => "Motorcycles",
"pid" => 0,
"children" => [
[
"id" => 1573695355619,
"name" => "Ducatti",
"pid" => 1573695348647
]
]
]
];
$getParentNode = getPathParent(1573695335102,$tree);
var_export($getParentNode);
// return:
/*
array (
0 => 1573695284631,
1 => 1573695305619,
2 => 1573695335102,
)
*/
$getParentNode = getPathParent(1573695335102,$tree,array('return'=>'name'));
var_export($getParentNode);
// return:
/*
array (
0 => 'Cars',
1 => 'BMW',
2 => 'X5',
)
*/
$getParentNode = getPathParent(1573695335102,$tree,array('id'=>'id','child'=>'children','return'=>'pid'));
var_export($getParentNode);
// return:
/*
array (
0 => 0,
1 => 1573695284631,
2 => 1573695305619,
)
*/
Please correct the terminologies that I am using.
I am trying to return a json data like this:
"data": [
{
"id": 1
"name": "test"
},
{
{
"id": 2
"name": "abc"
},
{
"id": 3
"name": "zxc"
}
]
and my code is exactly this one
$data = [];
foreach($prices as $price) {
$data[]["id"] = $price->id;
$data[]["name"] = $price->name;
}
$result["data"] = $data;
the code returns the json like this:
"data": [
{
"id": 1
},
{
"name": "test"
}
{
"id": 2
},
{
"name": "abc"
}
{
"id": 3
},
{
"name": "zxc"
}
]
Sorry for the bad formatting.
Like this
foreach($prices as $price) {
$data[] = [
"id"=> $price->id,
"name" => $price->name
];
}
You are adding the items sequentially, when you need to group them in an array and then add that array as a single unit.
$i = 0;
$data = [];
foreach($prices as $price) {
$data[$i]["id"] = $price->id;
$data[$i]["name"] = $price->name;
$i++;
}
$result["data"] = $data;
The problem is that you keep appending to the array instead of appending to an object and then to the $data array.
Try like this
$data = [];
foreach($prices as $price) {
$topush = [];
$topush["id"] = $price->id;
$topush["name"] = $price->name;
$data[] = $toReturn;
}
$result["data"] = $data;
or, even shorter
$data = [];
foreach($prices as $price) {
$data[] = ['id' => $price->id, 'name' => $price->name];
}
$result["data"] = $data;
You are adding two new Elements to your output, one containing the key/value for id and the other one for name. You have to put both into one element:
$data[]["id"] = $price->id; // Add one element
$data[]["name"] = $price->name; // Add second element
// New
$data[] = ['id' => $price->id, 'name' => $price->name]; // Add both as one Element
Empty [] creates new index every time. You must specify index where you wish to insert:
$data = [];
foreach($prices as $i => $price) {
$data[$i]["id"] = $price->id;
$data[$i]["name"] = $price->name;
}
$result["data"] = $data;
You kept assigning the value to a new key you should assign the data you want bundle in one go.
$data = [];
foreach($prices as $price) {
$data[] = [
"id" => $price->id,
"name" => $price->name;
]
}
$result["data"] = $data;
Hello I have a array like this.
$myarray = Array(
[0] => Array
(
[0] => Type
[1] => Brand
)
[1] => Array
(
[0] => Car
[1] => Toyota
)
)
I want result like this.
Type = Car
Brand = Toyota
So its mean From First Array "0" Value will be echo then from second array "0" Value will be shown.
Then From First array "1" value will be shown then from second array "1" Value will be shown.
Also I don't know how many array will be comes, So its need to be dynamic.
Any help Please?
You can try something like this (now i have tested it!):
foreach($myarray[0] as $titleKey=>$title) {
echo $title . " = ";
for($i = 1;$i<count($myarray);$i++) {
echo $myarray[$i][$titleKey] . ",";
}
echo "</br>";
}
Use array_combine for this
$myarray = [
['Type', 'Brand'],
['Car', 'Toyota']
];
list($fields, $values) = $myarray;
$output = array_combine($fields, $values);
echo json_encode($output, JSON_PRETTY_PRINT);
// {
// "Type": "Car",
// "Brand": "Toyota"
// }
But as you said, it could have more values than just the Toyota, so you'd have to do it like this
$myarray = [
['Type', 'Brand'],
['Car', 'Toyota'],
['Horse', 'Seabiscuit']
];
function first ($xs) { return $xs[0]; }
function rest ($xs) { return array_slice($xs, 1); }
$output = array_map(function ($values) use ($myarray) {
return array_combine(first($myarray), $values);
}, rest($myarray));
echo json_encode($output, JSON_PRETTY_PRINT);
// [
// {
// "Type": "Car",
// "Brand": "Toyota"
// },
// {
// "Type": "Horse",
// "Brand": "Seabiscuit"
// }
// ]
Note, this final solution assumes that the first array would contain the field names and the remaining arrays would have the values
Of course this works when more fields are added, too. No changes to the code are necessary.
$myarray = [
['Type', 'Brand', 'Origin'],
['Car', 'Toyota', 'Japan'],
['Horse', 'Seabiscuit', 'Kentucky']
];
function first ($xs) { return $xs[0]; }
function rest ($xs) { return array_slice($xs, 1); }
$output = array_map(function ($values) use ($myarray) {
return array_combine(first($myarray), $values);
}, rest($myarray));
echo json_encode($output, JSON_PRETTY_PRINT);
// [
// {
// "Type": "Car",
// "Brand": "Toyota",
// "Origin": "Japan"
// },
// {
// "Type": "Horse",
// "Brand": "Seabiscuit",
// "Origin": "Kentucky"
// }
// ]