Search code examples
phparray-merge

Merge two multidimensional arrays based on new keys only


Is there a way to merge those arrays:

// saved settings by the user
$arr1 = [
    'options' => [
        'first' => 1,
        'second' => 2,
        'third' => 3,
    ]
];

// new option schema (new keys added)
$arr2 = [
    'options' => [
        'first' => 1,
        'second' => 212,
        'fourth' => 4
    ]
];

and get the output as follows:

$arr3 = [
    'options' => [
        'first' => 1, // nothing do to, "first" key already exists
        'second' => 2, // nothing to do, "second" key exists (user already saved "second" with value 2, so we omit value 212)
         // "third" option got removed from new schema, no longer needed in the app, so may be removed from User settings as well
        'fourth' => 4 // this key is new, so let's add it to the final result
    ]
];

Basically I tried array_merge or array_merge_recursive however they merge all keys instead of new ones only, so it overrides user settings.

Of course the source array is way more complicated and has lots of multidimensional arrays inside.

Is there a way to make it easy or a library could handle it?


Solution

  • It can be done with a recursive function. The new structure ($arr2 in this example) defines the keys that exist in the result. If the old structure has a value at the corresponding key in the new structure, it will be used. If not, the value from the new structure will be used. Because you're only looking at keys that exist in the new structure, any keys from the old structure that no longer exist won't be included.

    function update($newKeys, $oldData) {
        $result = [];
    
        // iterate only new structure so obsolete keys won't be included
        foreach ($newKeys as $key => $value) {
    
            // use the old value for the new key if it exists
            if (isset($oldData[$key])) {
    
                if (is_array($oldData[$key]) && is_array($value)) {
                    // if the old and new values for the key are both arrays, recurse
                    $result[$key] = merge($value, $oldData[$key]);
                } else {
                    // otherwise, just use the old value
                    $result[$key] = $oldData[$key];
                }
    
            // use the new value if the key doesn't exist in the old values
            } else {
                $result[$key] = $value;
            }
        }
        return $result;
    }
    
    $result = update($arr2, $arr1);
    

    I would only use this on fully associative structures. If any of the inner arrays are basic indexed arrays where the keys don't matter, you'll have to add something to the function to keep it from messing them up.