Home > Software engineering >  Create a new array from a unknown depth multidimensional and keep the same structure
Create a new array from a unknown depth multidimensional and keep the same structure

Time:05-16

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 would be valid, so nothing will 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
            }
        }
    }
}

CodePudding user response:

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.

CodePudding user response:

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);

CodePudding user response:

  • 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

CodePudding user response:

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

  • Related