简体   繁体   中英

Create a new array from a unknown depth multidimensional and keep the same structure

I have a multidimensional array that can have any depth. What im trying to do is to filter the whole path based on dynamic keys and create a new array of it.

Example of the array

$originalArray = [
    "title" => "BACKPACK MULTICOLOUR",
    "description" => "description here",
    "images" => [
        [
            "id" => 12323123123,
            "width" => 635,
            "height" => 560,
            "src" => "https://example.com",
            "variant_ids": [32694976315473, 32863017926737],
        ],
        [
            "id" => 4365656656565,
            "width" => 635,
            "height" => 560,
            "src" => "https://example.com",
            "variant_ids": [32694976315473, 32863017926737],
        ]
    ],
    "price" => [
        "normal" => 11.00,
        "discount" => [
            "gold_members" => 9.00,
            "silver_members" => 10.00,
            "bronze_members" => null
        ]
    ]
];

Example how the output should look like with the key "title, width, height, gold_members" filtered out. Only keys from the end of the array tree should be valid, so nothing must happen when images is in the filter

$newArray = [
    "title" => "BACKPACK MULTICOLOUR",
    "images" => [
        [
            "width" => 635,
            "height" => 560,
        ],
        [
            "width" => 635,
            "height" => 560,
        ]
    ],
    "price" => [
        "discount" => [
            "gold_members" => 9.00,
        ]
    ]
];

I guess that i should create a function that loop through each element and when it is an associative array, it should call itself again

Because the filtered paths are unknown i cannot make a hardcoded setter like this:

$newArray["images"][0]["width"] = 635

The following filter will be an example but it should basically be dynamic

example what i have now:

$newArray = handleArray($originalArray);
    

handleArray($array) 
{
    $filter = ["title", "width", "height", "gold_members"];

    foreach ($array as $key => $value) {
        if (is_array($value)) {
            $this->handleArray($value);
        } else {
            if (in_array($key, $filter)) {
                // put this full path in the new array
            }
        }
    }
}

[Solved] Update:

I solved my problem thanks to @trincot

I used his code and added an extra check to add an array with multiple values to the new array

My code to solve the issue:

<?php
function isListOfValues($array) {            
    $listOfArrays = [];

    foreach ($array as $key => $value) {
        $listOfArrays[] = ! is_array($value) && is_int($key);
    }

    return array_sum($listOfArrays) === count($listOfArrays);
}

function filterKeysRecursive(&$arr, &$keep) {
    $result = [];

    foreach ($arr as $key => $value) {
        if (is_array($value) && ! isListOfValues($value)) {
            $value = filterKeysRecursive($value, $keep);
            
            if (count($value)) {
                $result[$key] = $value;
            }                    
        } else if (array_key_exists($key, $keep)) {
            $result[$key] = $value;
        }
    }
    
    return $result;
}

$keep = array_flip(["title", "width", "height", "gold_members"]);
        
$result = filterKeysRecursive($originalArray, $keep);

My proposition to you is to write a custom function to transform structure from one schema to another:

function transform(array $originalArray): array {
    array_walk($originalArray['images'], function (&$a, $k) {
      unset($a['id']); unset($a['src']);
    });
    unset($originalArray['description']);
    unset($originalArray['price']['normal']);
    unset($originalArray['price']['discount']['silver_members']);
    unset($originalArray['price']['discount']['bronze_members']);
    
    return $originalArray;
}
var_dump(transform($originalArray));

If you are familiar with OOP I suggest you to look at how DTO works in API Platform for example and inject this idea into your code by creating custom DataTransformers where you specify which kind of structers you want to support with transformer and a method where you transform one structure to another.

You could use a recursive function, with following logic:

  • base case: the value associated with a key is not an array (it is a "leaf"). In that case the new object will have that key/value only when the key is in the list of desired keys.

  • recursive case: the value associated with a key is an array. Apply recursion to that value. Only add the key when the returned result is not an empty array. In that case associate the filtered value to the key in the result object.

To speed up the look up in the list of keys, it is better to flip that list into an associative array.

Here is the implementation:

function filter_keys_recursive(&$arr, &$keep) {
    foreach ($arr as $key => $value) {
        if (is_array($value)) {
            $value = filter_keys_recursive($value, $keep);
            if (count($value)) $result[$key] = $value;
        } else if (array_key_exists($key, $keep)) {
            $result[$key] = $value;
        }
    }
    return $result;
}

$originalArray = ["title" => "BACKPACK MULTICOLOUR","description" => "description here","images" => [["id" => 12323123123,"width" => 635,"height" => 560,"src" => "https://example.com"],["id" => 4365656656565,"width" => 635,"height" => 560,"src" => "https://example.com"]],"price" => ["normal" => 11.00,"discount" => ["gold_members" => 9.00,"silver_members" => 10.00,"bronze_members" => null]]];

$keep = array_flip(["title", "width", "height", "gold_members"]);

$result = filter_keys_recursive($originalArray, $keep);
  • Iterate over the array recursively on each key and subarray.
  • If the current key in the foreach is a required key in the result then:
    • If the value is not an array, simply assign the value
    • If the value is an array, iterate further down over value recursively just in case if there is any other filtering of the subarray keys that needs to be done.
  • If the current key in the foreach is NOT a required key in the result then:
    • Iterate over value recursively if it's an array in itself. This is required because there could be one of the filter keys deep down which we would need. Get the result and only include it in the current subresult if it's result is not an empty array. Else, we can skip it safely as there are no required keys down that line.

Snippet:

<?php

function filterKeys($array, $filter_keys) {
    $sub_result = [];
    foreach ($array as $key => $value) {
        if(in_array($key, $filter_keys)){// if $key itself is present in $filter_keys
            if(!is_array($value)) $sub_result[$key] = $value;       
            else{
                $temp = filterKeys($value, $filter_keys);
                $sub_result[$key] = count($temp) > 0 ? $temp : $value;
            }
        }else if(is_array($value)){// if $key is not present in $filter_keys - iterate over the remaining subarray for that key
            $temp = filterKeys($value, $filter_keys);
            if(count($temp) > 0) $sub_result[$key] = $temp;
        }
    }
    
    return $sub_result;
}

$result = filterKeys($originalArray, ["title", "width", "height", "gold_members"]);

print_r($result);

Online Demo

Try this way.

    $expectedKeys = ['title','images','width','height','price','gold_members'];

    function removeUnexpectedKeys ($originalArray,$expectedKeys)
    {
        foreach ($originalArray as $key=>$value) {
          if(is_array($value)) {
            $originalArray[$key] = removeUnexpectedKeys($value,$expectedKeys);
            if(!is_array($originalArray[$key]) or count($originalArray[$key]) == 0) {
                unset($originalArray[$key]);
            }
          } else {
              if (!in_array($key,$expectedKeys)){
                  unset($originalArray[$key]);
              }
          }
        }
        return $originalArray;
    }
    
    $newArray = removeUnexpectedKeys ($originalArray,$expectedKeys);
    print_r($newArray);

check this on editor, https://www.online-ide.com/vFN69waXMf

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM