Loop through every occurrence of specific child key in a nested structure - php

I have this payload,
[
{
"id": 1,
"name": "T-Shirt",
"children_rec": [
{
"id": 2,
"name": "Classic",
"children_rec": [
{
"id": 3,
"name": "Lycra",
"children_rec": []
}
]
},
{
"id": 4,
"name": "Plain",
"children_rec": []
}
]
},
{
"id": 5,
"name": "Shirt",
"children_rec": [
{
"id": 6,
"name": "Plain",
"children_rec": []
}
]
}
]
I want to loop through every occurrence of children_rec.
What I have tried is,
foreach ($mainCategories as $category) {
if (!empty($category['children_rec'])) {
foreach ($category['children_rec'] as $child) {
if (!empty($category['children_rec'])) {
var_dump($child);
}
}
}
}
But this is not the dynamic way to achieve this. What if I have 5 or 6 level of childer_rec. How can I achieve this?
Edit
#ggorlen's way is cool, but What if I need this output?
[
{
"id": 1,
"name": "T-Shirt",
"children": [
"2",
"3",
"4"
]
},
{
"id": 5,
"name": "Shirt",
"children": [
"6"
]
}
]

Since you don't know the depth, you'll need a stack or recursion. Here's a solution with a stack:
<?php
$tree = json_decode('[ { "id": 1, "name": "T-Shirt", "children_rec": [ { "id": 2, "name": "Classic", "children_rec": [ { "id": 3, "name": "Lycra", "children_rec": [] } ] }, { "id": 4, "name": "Plain", "children_rec": [] } ] }, { "id": 5, "name": "Shirt", "children_rec": [ { "id": 6, "name": "Plain", "children_rec": [] } ] } ]', true);
for ($stack = $tree; !empty($stack);) {
$curr = array_pop($stack);
$flattened[] = $curr["name"]; // or just $curr if you only want the node
if (!empty($curr["children_rec"])) {
array_push($stack, ...$curr["children_rec"]);
}
}
print_r($flattened);
Array
(
[0] => Shirt
[1] => Plain
[2] => T-Shirt
[3] => Plain
[4] => Classic
[5] => Lycra
)
If order is important, you can array_reverse all arrays as you push them onto the stack without harming the linear time complexity.
For the updated specification, just plop the above code into a function and call it for each root node:
<?php
function flatten($tree) {
for ($stack = array_reverse($tree); !empty($stack);) {
$curr = array_pop($stack);
$flattened[] = $curr["id"];
if (!empty($curr["children_rec"])) {
array_push($stack, ...array_reverse($curr["children_rec"]));
}
}
return $flattened;
}
$tree = json_decode('[ { "id": 1, "name": "T-Shirt", "children_rec": [ { "id": 2, "name": "Classic", "children_rec": [ { "id": 3, "name": "Lycra", "children_rec": [] } ] }, { "id": 4, "name": "Plain", "children_rec": [] } ] }, { "id": 5, "name": "Shirt", "children_rec": [ { "id": 6, "name": "Plain", "children_rec": [] } ] } ]', true);
foreach ($tree as &$root) {
$root["children"] = flatten($root["children_rec"]);
unset($root["children_rec"]);
}
echo json_encode($tree, JSON_PRETTY_PRINT)."\n";
Output:
[
{
"id": 1,
"name": "T-Shirt",
"children": [
2,
3,
4
]
},
{
"id": 5,
"name": "Shirt",
"children": [
6
]
}
]

Related

multidimensional array merge into one array if key is same without using nested loop

[
{
"status": true,
"data": [
{
"id": 2,
"name": "Algeria",
"country_code": "DZ",
"regions": [
{
"id": 2,
"country_id": 2,
"region_code": "RG01",
"depots": [
{
"id": 5,
"region_id": 2,
"depot_code": "DP04",
"depot_name": "North Depot",
"area": [
{
"id": 1,
"depot_id": 5,
"area_code": "AR1",
"area_name": "Area-1"
},
{
"id": 2,
"depot_id": 5,
"area_code": "AR2",
"area_name": "Area-2"
}
]
},
{
"id": 6,
"region_id": 2,
"depot_code": "DP06",
"depot_name": "east Depot",
"area": [
{
"id": 3,
"depot_id": 6,
"area_code": "AR3",
"area_name": "Area-3"
}
]
}
]
},
{
"id": 3,
"country_id": 2,
"region_code": "RG02",
"depots": []
}
]
}
]
}
]
i want to merge area into single array like
"area": [
{
"id": 1,
"depot_id": 5,
"area_code": "AR1",
"area_name": "Area-1"
},
{
"id": 2,
"depot_id": 5,
"area_code": "AR2",
"area_name": "Area-2"
},
{
"id": 3,
"depot_id": 6,
"area_code": "AR3",
"area_name": "Area-3"
}
]
i don't want to use multiple foreach.
thanks in advance.
You can use the following code to recursively iterate of all your structure and extract only data you need (by key). I assume $data has your original data structure and within $result you will get array of area:
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($data), RecursiveIteratorIterator::SELF_FIRST);
$result = [];
foreach ($it as $k => $v) {
if ($k === 'area') {
$result = array_merge($result, $v);
}
}
I would not overcomplicate things, loop through you data structure. Add all areas to a collection. Prepare your data structure and i added a check to secure there is no duplicates with unique().
$data = 'your-data';
$areas = collect();
foreach($data->data as $country) {
foreach($country->regions as $region) {
foreach ($region->depots as $depot) {
$areas->concat($depot->area);
}
}
}
$result = ['area' => $areas->unique('id')->all()];

PHP Returning JSON with modified data

I want my categories list be nice formatted while return to user. What I get from database is:
[
{
"id": 1,
"name": "pet",
"parent_id": null
},
{
"id": 2,
"name": "page",
"parent_id": null
},
{
"id": 3,
"name": "dog",
"parent_id": 1
},
{
"id": 4,
"name": "cat",
"parent_id": 1
},
{
"id": 5,
"name": "rodent",
"parent_id": 1
},...
I want it to keep tree structure, like:
{
"id": 1,
"name": "pet",
"parent_id": null,
"children": [
{
"id": 3,
"name": "dog",
"parent_id": 1
},
{
"id": 4,
"name": "cat",
"parent_id": 1
},...
etc.
Is there an easy way way to do it or I have to loop through the database results and create new organized array to return?
What is the best approach to do that? The problem is that the subcategories could also have subcategories. Or maybe i should just keep structure I get from the database and add children id's as an array (as I can refer to them anyway)?
I would be grateful for your help.
Thank you.
Solution:
function normalize_db_animals(){
$values[] = ["id" => 1, "name" => "pet", "parent_id" => null];
$values[] = ["id" => 2, "name" => "dog", "parent_id" => 1];
$values[] = ["id" => 3, "name" => "cat", "parent_id" => 1];
$values[] = ["id" => 4, "name" => "rodent", "parent_id" => 1];
$values[] = ["id" => 5, "name" => "wild", "parent_id" => null];
$values[] = ["id" => 6, "name" => "tiger", "parent_id" => 5];
$values[] = ["id" => 7, "name" => "rhino", "parent_id" => 5];
$normalize = function () use ($values) {
$tree = [];
$i = 0;
do {
$pet = $values[$i];
if ($pet['parent_id']) {
if (array_key_exists($pet['parent_id'], $tree)) {
$tree[$pet['parent_id']]['children'][] = $pet;
}
} else {
$tree[$pet['id']] = $pet;
}
$i++;
} while ($i < count($values));
return $tree;
};
$tree = $normalize();
echo json_encode($tree);
}
Result:
{"1":{"id":1,"name":"pet","parent_id":null,"children":[{"id":2,"name":"dog","parent_id":1},{"id":3,"name":"cat","parent_id":1},{"id":4,"name":"rodent","parent_id":1}]},"5":{"id":5,"name":"wild","parent_id":null,"children":[{"id":6,"name":"tiger","parent_id":5},{"id":7,"name":"rhino","parent_id":5}]}}
Try this one
$a = json_decode('[{
"id": 1,
"name": "pet",
"parent_id": null
},
{
"id": 2,
"name": "page",
"parent_id": null
},
{
"id": 3,
"name": "dog",
"parent_id": 1
},
{
"id": 4,
"name": "cat",
"parent_id": 1
},
{
"id": 5,
"name": "rodent",
"parent_id": 4
},
{
"id": 6,
"name": "rodent",
"parent_id": 2
}]');
$a = collect($a);
$filtered = $a;
foreach ($filtered as $key => $value) {
$children = $a->where('parent_id', $value->id);
if(!$children->isEmpty()){
$value->children = $children;
$filtered->forget(array_values(array_keys($children->toArray())));
}
}
dd($filtered);

PHP - Moving a key within an array and flatten it

I have this array of object:
[
{
"id": 1,
"name": "Carbo",
"menus": [
{
"id": 33,
"name": "FloralWhite",
"image": {
"web": "https://lorempixel.com/640/360/food/?89722",
"mobile": "https://lorempixel.com/640/360/food/?89722",
"square": "https://lorempixel.com/640/360/food/?89722"
},
"logs": {
"price": 2
}
},
{
"id": 40,
"name": "LightGray",
"image": {
"web": "https://lorempixel.com/640/360/food/?63930",
"mobile": "https://lorempixel.com/640/360/food/?63930",
"square": "https://lorempixel.com/640/360/food/?63930"
},
"logs": {
"price": 2
}
},
]
}
]
What I want to achieve:
[
{
"id": 1,
"name": "Carbo",
"menus": [
{
"id": 33,
"name": "FloralWhite",
"image": {
"web": "https://lorempixel.com/640/360/food/?89722",
"mobile": "https://lorempixel.com/640/360/food/?89722",
"square": "https://lorempixel.com/640/360/food/?89722"
},
"price": 2
},
{
"id": 40,
"name": "LightGray",
"image": {
"web": "https://lorempixel.com/640/360/food/?63930",
"mobile": "https://lorempixel.com/640/360/food/?63930",
"square": "https://lorempixel.com/640/360/food/?63930"
},
"price": 2
},
]
}
]
I've tried using laravel collection flatten with depth but it fail
$data = collect($data->menus);
$data = $data->flatten(1);
$data->values()->all();
How can I flatten the menus['logs'] object so it can one level with menu?
Something like this should do the trick. Simply iterate over your array, set the new property and remove the one you don't want.
$data = json_decode("[{
\"id\": 1,
\"name\": \"Carbo\",
\"menus\": [
{
\"id\": 33,
\"name\": \"FloralWhite\",
\"image\": {
\"web\": \"https://lorempixel.com/640/360/food/?89722\",
\"mobile\": \"https://lorempixel.com/640/360/food/?89722\",
\"square\": \"https://lorempixel.com/640/360/food/?89722\"
},
\"logs\": {
\"price\": 2
}
},
{
\"id\": 40,
\"name\": \"LightGray\",
\"image\": {
\"web\": \"https://lorempixel.com/640/360/food/?63930\",
\"mobile\": \"https://lorempixel.com/640/360/food/?63930\",
\"square\": \"https://lorempixel.com/640/360/food/?63930\"
},
\"logs\": {
\"price\": 2
}
}
]
}]");
foreach($data as $val){
foreach($val->menus as $menuVal){
$menuVal->price = $menuVal->logs->price;
unset($menuVal->logs);
}
}
#Stefen Suhat i really don't know what is the syntax for laravel but i did it with a simple php foreach() hope this will help you, as your array's depth is long so i had gone to all the levels of your array, check code and output snippet here https://eval.in/806879
try below one:
<?php
$array = array(
array(
"id"=> 1,
"name"=> "Carbo",
"menus"=> array(
array(
"id"=> 33,
"name"=> "FloralWhite",
"image"=> array(
"web"=> "https=>//lorempixel.com/640/360/food/?89722",
"mobile"=> "https=>//lorempixel.com/640/360/food/?89722",
"square"=> "https=>//lorempixel.com/640/360/food/?89722"
),
"logs"=> array(
"price"=> 2
)
),
array(
"id"=> 40,
"name"=> "LightGray",
"image"=> array(
"web"=> "https=>//lorempixel.com/640/360/food/?63930",
"mobile"=> "https=>//lorempixel.com/640/360/food/?63930",
"square"=> "https=>//lorempixel.com/640/360/food/?63930"
),
"logs"=> array(
"price"=> 2
)
),
)
)
);
foreach($array as $key => $value){
foreach ($value as $key1 => $value1) {
if(is_array($value1) && $key1 == "menus"){
foreach($value1 as $key2 => $value2) {
foreach ($value2 as $key3 => $value3) {
if(is_array($value3) && $key3 == "logs"){
unset($array[$key][$key1][$key2][$key3]);
$array[$key][$key1][$key2] = array_merge($array[$key][$key1][$key2], $value3);
}
}
}
}
}
}
echo "array after<br>";
echo "<pre>";
print_r($array); //your array after
?>

Laravel 5.3 - Merge array based on 2 matching keys?

Given the following two arrays, how can they be merged efficiently to result in the third array?
productData
$productData =
[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]
products
$products =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}
productsFinal ($productData merged into $products - based on matching combo of product_id/product_id and type/product_type)
$productsFinal =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6,
// How to merge product data and wrap with "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
},
{
"id": 6,
"type": "electronic",
"product_id": 5,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
}
},
{
"id": 9,
"type": "kitchen",
"product_id": 4,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
}
]
}
I tried different things for the outcome in a foreach loop but still cannot get it to render as intended:
foreach($productData as $productDataItem) {
// when $productDataItem.product_id == $product.product_id && $productDataItem.type == $product.product_type
// move the matching $productDataItem object into matching $product object, wrapped in a new "data" key
}
I don't know Laravel too well. However you can join your data objects quite easily:
<?php
$productData = json_decode('[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]');
$products = json_decode('{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}');
// combine both data objects
foreach($products->data As &$p) {
foreach($productData As $d) {
if(property_exists($p, "product_id") && property_exists($d, "product_id") && property_exists($p, "type") && property_exists($d, "type")) {
if($p->product_id==$d->product_id && $p->type==$d->type) {
//$p = (object) array_merge((array) $p, (array) $d);
$p->data = $d; // updated answer
continue;
}
}
}
}
echo("<pre>");
echo json_encode($products, JSON_PRETTY_PRINT);
?>
You can test the code here: http://sandbox.onlinephpfunctions.com/code/98a50c35ee32c30f0d2be1661f7afb5895174cbe
Update: http://sandbox.onlinephpfunctions.com/code/aeebfdcf4f4db5e960260e931982570cfed19e0e
I would suggest to check this package dingo/api. I assume you want to display some kind of JSON response. Take a look at Transformers. You can do something like this :
<?php
namespace App\Http\Transformers;
use App\Http\Controllers\ProductData;
use League\Fractal\TransformerAbstract;
class ProductsDataTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(ProductData $productdata)
{
return [
'id' => $productdata->id,
'product_type' => $productdata->product_type,
'product /*or data*/' => Product::find($productdata->product_id),
];
}
}
This would find the product by it's ID and look like this :
{
"id": 4,
"product_type": "electronic",
"product" {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
},
},
You can then also create a transformer for Product to take care of your specs attribute to do the same thing.

PHP Assotiative array -> recursive traverse not working

I have an associative array object :
[ {
"id": 15,
"owner_id": 1,
"container_info": {
"id": 1,
"container_id": 15
},
"filters": [
{
"id": 3,
"parent_id": null
},
{
"id": 6,
"parent_id": null
}
],
"children_recursive": [
{
"id": 7,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
{
"id": 8,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
]
}
]
},
{
"id": 16,
"owner_id": 1,
"container_info": {
"id": 2,
"container_id": 16
},
"filters": [
],
"children_recursive": [
]
},
]
}
]
I want to recursively loop through all object and their children_recursive key. And each children_recursive object (at any depth) needs to be processed
So I used :
public function traverseContainerRecursively($containerItems)
{
Log:info(' CHECK 1');
foreach ($containerItems as $containerItem) {
Log::info(json_encode($containerItem->id));
Log::info(json_encode($containerItem->owner_id));
Log::info(json_encode($containerItem->container_info));
Log::info(json_encode($containerItem->children_recursive));
}
Log::info(' CHECK 2');
foreach ($containerItems as $containerItem) {
Log::info(json_encode($containerItem['id']));
Log::info(json_encode($containerItem['owner_id']));
Log::info(json_encode($containerItem['container_info']));
Log::info(json_encode($containerItem['children_recursive']));
}
Log::info(' CHECK 3');
foreach ($containerItems as $key=>$value) {
if( $key == "children_recursive" ) {
Log::info(json_encode($value));
$this->traverseContainerRecursively($value);
}
} //foreach end
}
OUTPUT :
CHECK 1
15
1
null
null
CHECK 2
15
1
null
null
CHECK 3
{
"id": 15,
"owner_id": 1,
"container_info": {
"id": 1,
"container_id": 15
},
"filters": [
{
"id": 3,
"parent_id": null
},
{
"id": 6,
"parent_id": null
}
],
"children_recursive": [
{
"id": 7,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
{
"id": 8,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
]
}
]
},
{
"id": 16,
"owner_id": 1,
"container_info": {
"id": 2,
"container_id": 16
},
"filters": [
],
"children_recursive": [
]
},
]
} // i.e. the entire passed object
So I am unable to retrieve the value for key "children_recursive".
Please guide.
I figured out the issue.
Following code worked:
$obj = json_decode($containerItem);
Log::info(json_encode($obj->children_recursive));

Categories