27

There are plenty of tips and code examples out there of accessing PHP arrays with dot notation, but I would like to do somewhat the opposite. I would like to take a multidimensional array like this:

$myArray = array(
    'key1' => 'value1',
    'key2' => array(
        'subkey' => 'subkeyval'
    ),
    'key3' => 'value3',
    'key4' => array(
        'subkey4' => array(
            'subsubkey4' => 'subsubkeyval4',
            'subsubkey5' => 'subsubkeyval5',
        ),
        'subkey5' => 'subkeyval5'
    )
);

And turn it into this (likely through some recursive function):

$newArray = array(
    'key1'                    => 'value1',
    'key2.subkey'             => 'subkeyval',
    'key3'                    => 'value3',
    'key4.subkey4.subsubkey4' => 'subsubkeyval4',
    'key4.subkey5.subsubkey5' => 'subsubkeyval5',
    'key4.subkey5'            => 'subkeyval5'
);
TheCheese
  • 361
  • 1
  • 3
  • 7
  • I thought array_walk_recursive might be able to help me to build the new keys since it seemed like it could do a lot of the heavy lifting with recursion but it doesn't provide *all* the keys of the array. For instance, using array_walk_recursive on $myArray (as run through the example function on the PHP documentation page) would only provide me with the keys that don't have array values. I'm continuing to attempt writing my own recursive function with some good old foreach looping but it's been a long day and is hurting my head. I'll continue to go at it and update if I get it (or any closer) – TheCheese May 03 '12 at 02:58
  • 2
    Laravel has `Illuminate\Support\Arr::dot($the_array)` to do it, it can be tested in `php artisan tinker`. – DevonDahon Mar 23 '21 at 10:40

5 Answers5

87

teh codez

$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($ritit as $leafValue) {
    $keys = array();
    foreach (range(0, $ritit->getDepth()) as $depth) {
        $keys[] = $ritit->getSubIterator($depth)->key();
    }
    $result[ join('.', $keys) ] = $leafValue;
}

output

Array
(
    [key1] => value1
    [key2.subkey] => subkeyval
    [key3] => value3
    [key4.subkey4.subsubkey4] => subsubkeyval4
    [key4.subkey4.subsubkey5] => subsubkeyval5
    [key4.subkey5] => subkeyval5
)

demo: http://codepad.org/YiygqxTM

I need to go, but if you need an explanation of that tomorrow, ask me.

goat
  • 31,486
  • 7
  • 73
  • 96
  • 1
    Now, how about the reverse of this function? – Petah Jan 13 '13 at 04:36
  • 1
    @Petah, see http://stackoverflow.com/q/9635968/1388892 @ rambocoder, what is a "ritit"? I mean the word... thx! --> ah, RecursiveITeratorITerator... righty right :) – Adrian Föder Mar 17 '14 at 10:38
  • I love this. Built a custom function before I saw this. This is slightly slower but nicely handles index-less arrays/sub-arrays. Nice one! – Ema4rl Feb 15 '16 at 11:51
6

There is already the answer with RecursiveIteratorIterator. But here is a more optimal solution, that avoids using nested loops:

$iterator = new RecursiveIteratorIterator(
    new RecursiveArrayIterator($arr),
    RecursiveIteratorIterator::SELF_FIRST
);
$path = [];
$flatArray = [];

foreach ($iterator as $key => $value) {
    $path[$iterator->getDepth()] = $key;

    if (!is_array($value)) {
        $flatArray[
            implode('.', array_slice($path, 0, $iterator->getDepth() + 1))
        ] = $value;
    }
}

There are several points need to be made here. Notice the use of RecursiveIteratorIterator::SELF_FIRST constant here. It is important as the default one is RecursiveIteratorIterator::LEAVES_ONLY which wouldn't let us access all keys. So with this constant set, we start from the top level of an array and go deeper. This approach lets us store the history of keys and prepare the key when we rich leaf using RecursiveIteratorIterator::getDepth method.

Here is a working demo.

sevavietl
  • 3,762
  • 1
  • 14
  • 21
5

This will handle an arbitrary level of nesting:

<? //PHP 5.4+
$dotFlatten = static function(array $item, $context = '') use (&$dotFlatten){
    $retval = [];
    foreach($item as $key => $value){
        if (\is_array($value) === true){
            foreach($dotFlatten($value, "$context$key.") as $iKey => $iValue){
                $retval[$iKey] = $iValue;
            }
        } else {
            $retval["$context$key"] = $value;
        }
    }
    return $retval;
};

var_dump(
    $dotFlatten(
        [
            'key1' => 'value1',
            'key2' => [
                'subkey' => 'subkeyval',
            ],
            'key3' => 'value3',
            'key4' => [
                'subkey4' => [
                    'subsubkey4' => 'subsubkeyval4',
                    'subsubkey5' => 'subsubkeyval5',
                ],
                'subkey5' => 'subkeyval5',
            ],
        ]
    )
);
?>
Cory Carson
  • 276
  • 1
  • 6
2

This is my take on a recursive solution, which works for arrays of any depth:

function convertArray($arr, $narr = array(), $nkey = '') {
    foreach ($arr as $key => $value) {
        if (is_array($value)) {
            $narr = array_merge($narr, convertArray($value, $narr, $nkey . $key . '.'));
        } else {
            $narr[$nkey . $key] = $value;
        }
    }

    return $narr;
}

Which can be called as $newArray = convertArray($myArray).

blafrat
  • 339
  • 2
  • 7
1

This another approach similar to Blafrat above - but handles simply arrays as values.

 function dot_flatten($input_arr, $return_arr = array(), $prev_key = '')
 {
     foreach ($input_arr as $key => $value)
     {
        $new_key = $prev_key . $key;

        // check if it's associative array 99% good
        if (is_array($value) && key($value) !==0 && key($value) !==null)
        {
            $return_arr = array_merge($return_arr, dot_flatten($value, $return_arr, $new_key . '.'));
        }
        else
        {
            $return_arr[$new_key] = $value;
        }
    }

    return $return_arr;
}

(The only case this wouldn't catch is where you had a value that was associative but the first key was 0.)

Note that the RecursiveIteratorIterator can be slower than regular recursive function. https://xenforo.com/community/threads/php-spl-why-is-recursiveiteratoriterator-100x-slower-than-recursive-search.57572/

In this case using the sample array given for 1000 iterations php5.6, this code is twice as fast (recursive=.032 vs interator=.062) - but the difference is probably insignificant for most cases. Mainly I prefer recursive because I find the logic of the Iterator needlessly complicated for a simple use case like this.

Yehosef
  • 17,987
  • 7
  • 35
  • 56