Search code examples
phparraysrecursionadyen

Expand "dot notation" keys in a nested array to child arrays


I start with a nested array of some arbitrary depth. Within that array, some keys are a series of tokens separated by dots. For example "billingAddress.street" or "foo.bar.baz". I would like to expand those keyed elements to arrays, so the result is a nested array with all those keys expanded.

For example:

[
    'billingAddress.street' => 'My Street',
    'foo.bar.baz' => 'biz',
]

should be expanded to:

[
    'billingAddress' => [
        'street' => 'My Street',
    ],
    'foo' => [
        'bar' => [
            'baz' => 'biz',
        ]
    ]
]

The original "billingAddress.street" can be left alongside the new "billingAddress" array, but it does not need to be (so the solution may operate on the original array or create a new array). Other elements such as "billingAddress.city" may need to be added to the same expanded portion of the array.

Some keys may have more than two tokens separated by dots, so will need to be expanded deeper.

I've looked at array_walk_recursive() but that only operates on elements. For each matching element key, I actually want to modify the parent array those elements are in.

I've looked at array_map, but that does not provide access to the keys, and as far as I know is not recursive.

An example array to expand:

[
    'name' => 'Name',
    'address.city' => 'City',
    'address.street' => 'Street',
    'card' => [
        'type' => 'visa',
        'details.last4' => '1234',
    ],
]

This is to be expanded to:

[
    'name' => 'Name',
    'address.city' => 'City', // Optional
    'address' => [
        'city' => 'City',
        'street' => 'Street',
    ],
    'address.street' => 'Street', // Optional
    'card' => [
        'type' => 'visa',
        'details.last4' => '1234', // Optional
        'details' => [
            'last4' => '1234',
        ],
    ],
]

What I think I need, is something that walks to each array in the nested array and can apply a user function to it. But I do suspect I'm missing something obvious. The payment gateway I am working with sends me this mix of arrays and "pretend arrays" using the dot-notation, and my objective is to normalize it into an array for extracting portions.

I believe the problem differs from similar questions on SO due to this mix of arrays and non-arrays for expanding. Conceptually it is a nested array where sound groups of elements at any level need to be replaced with new arrays, so there are two levels of recursion happening here: the tree walking, and the expansion, and then the walking of the expanded trees to see if there is more expansion needed.


Solution

  • You could find it useful to reverse the order of the keys you get from exploding the combined (dotted) key. In that reversed order it is easier to progressively wrap a previous result into a new array, thereby creating the nested result for one dotted key/value pair.

    Finally, that partial result can be merged into the accumulated "grand" result with the built-in array_merge_recursive function:

    function expandKeys($arr) {
        $result = [];
        foreach($arr as $key => $value) {
            if (is_array($value)) $value = expandKeys($value);
            foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
            $result = array_merge_recursive($result, $value);
        }
        return $result;
    }