45

I'm looking for some tool to give me a recursive diff of two arrays. What I envision is a web page with two color-coded tree-structures. On each tree, green are parts of the array which match in both arrays, and red is for parts of each that don't match the other. Something like the output of dBug

I have some code that gives me a nested array to populate a report. I'm developing a new method that should be faster, but I need to test the values and also the structure, to make sure it gives output identical to the old method.

Is there something out there that I can use? Or do I need to write this? Or is there another way to accomplish my goals?

user151841
  • 17,377
  • 29
  • 109
  • 171
  • Is it _just_ for testing those outputs temporarily, or for longer use? It it's for a test, a simple `wdiff` over `var_export` output should do the trick... – Wrikken Oct 06 '10 at 20:30
  • In the nested structure, if one element is an array of 6, while the other is an array of 3, will that trip up `wdiff`? Because in the output, say from lines 0-30 it will be identical, and from the end back to line 36 it will be identical. It's only those middle lines that will be different -- 3 versus 6. If wdiff looks at this, will it get tripped up? – user151841 Oct 06 '10 at 20:42
  • The output isn't gonna be pretty split in key/value pairs, it will however try hard to match lines before & after for a match further on, and IMHO, if I'm just diffing to check, it'll do. Just use a simple [testscript here](http://pastebin.com/wrwXw5zT) and see if is good enough for your purpose. The alternative is a recursive function, not that hard either, but more work. – Wrikken Oct 06 '10 at 21:03
  • just an idea: if you need something really fast you can test by converting it to strings first with json_encode, compare two strings, if there is a difference, go into the recursive check after that. – visualex Feb 26 '20 at 09:17

6 Answers6

74

There is one such function implemented in the comments of array_diff.

function arrayRecursiveDiff($aArray1, $aArray2) {
  $aReturn = array();

  foreach ($aArray1 as $mKey => $mValue) {
    if (array_key_exists($mKey, $aArray2)) {
      if (is_array($mValue)) {
        $aRecursiveDiff = arrayRecursiveDiff($mValue, $aArray2[$mKey]);
        if (count($aRecursiveDiff)) { $aReturn[$mKey] = $aRecursiveDiff; }
      } else {
        if ($mValue != $aArray2[$mKey]) {
          $aReturn[$mKey] = $mValue;
        }
      }
    } else {
      $aReturn[$mKey] = $mValue;
    }
  }
  return $aReturn;
} 

The implementation only handles two arrays at a time, but I do not think that really posses a problem. You could run the diff sequentially if you need the diff of 3 or more arrays at a time. Also this method uses key checks and does a loose verification.

LWC
  • 1,084
  • 1
  • 10
  • 28
mhitza
  • 5,709
  • 2
  • 29
  • 52
  • @Zend_Sklave, Since mhitza's answer worked for you, you should maybe mark it as actually answering your request... – Jon L. Apr 20 '12 at 06:04
  • 7
    @JonL. I think he would have to be the author of the question in order to do that :) – user151841 Aug 10 '12 at 15:10
  • 7
    This function returns a lot of data that has not changed at all. I don't see how that is useful. – CommaToast Nov 11 '14 at 02:06
  • 3
    Furthermore, if you really want to get the difference between two arrays, you need to run this function **twice** to not only find what is part of array 1 and not part of array 2 but also what is part of array 2 but not part of array 1. Might be obvious to some ... but not to others (like me at first ;) ) – Fabian Becker Aug 02 '16 at 06:37
  • @CommaToast can you provide an example of when it returns data that hasn't changed? For my couple tests, it only returns the difference. – Jeff Puckett Oct 24 '16 at 20:12
  • Please see my answer for an updated version of this function that matches how `array_diff` works (plus an explanation of why this answer is broken). – treeface Nov 01 '16 at 19:38
  • The compare value line should be with an extra = like `if ($mValue !== $aArray2[$mKey]) {`, this way it will also detect it when the value type is changed. – KoalaBear Jul 06 '17 at 09:58
  • For this function to be compatible with array_diff_assoc; `if ($mValue != $aArray2[$mKey]) {` line should be `if ((string)$mValue !== (string)$aArray2[$mKey]) {` as documented in php.net. Otherwise this function returns wrong value for `$a = ['c' => false]; $b = ['c' => 0];` – Göktuğ Öztürk Aug 16 '18 at 13:41
  • 1
    This example actually doesn't work. Easy to check `$a = [1,2,3,4]; $b = [4,5,6,7]; arrayRecursiveDiff($a, $b);` – klipach Oct 01 '18 at 14:13
18

The accepted answer is close to correct, but it doesn't really emulate array_diff correctly.

There are two problems that largely revolve around key matching:

  1. array_diff has a specific behavior where it does not produce a result for an array key that is completely missing from the second array if its value is still in the second array. If you have two arrays $first = ['foo' => 2, 'moo' => 2] and $second = ['foo' => 2], using the accepted answer's function the output will be ['moo' => 2]. If you run the same arrays through array_diff, it will produce an empty array. This is because the above function's final else statement adds it to the diff if the array key is missing, but that's not the expected behavior from array_diff. The same is true with these two arrays: $first = ['foo' => 1] and $second = [1]. array_diff will produce an empty array.

  2. If two arrays have the same values but different keys, it returns more values than expected. If you have two arrays $foo = [1, 2] and $moo = [2, 1], the function from the accepted answer will output all values from $foo. This is because it's doing a strict key matching on each iteration where it finds the same key (numerical or otherwise) in both arrays instead of checking all of the other values in the second array.

The following function is similar, but acts more closely to how you'd expect array_diff to work (also with less silly variable names):

function array_diff_recursive($arr1, $arr2)
{
    $outputDiff = [];

    foreach ($arr1 as $key => $value)
    {
        //if the key exists in the second array, recursively call this function 
        //if it is an array, otherwise check if the value is in arr2
        if (array_key_exists($key, $arr2))
        {
            if (is_array($value))
            {
                $recursiveDiff = array_diff_recursive($value, $arr2[$key]);

                if (count($recursiveDiff))
                {
                    $outputDiff[$key] = $recursiveDiff;
                }
            }
            else if (!in_array($value, $arr2))
            {
                $outputDiff[$key] = $value;
            }
        }
        //if the key is not in the second array, check if the value is in 
        //the second array (this is a quirk of how array_diff works)
        else if (!in_array($value, $arr2))
        {
            $outputDiff[$key] = $value;
        }
    }

    return $outputDiff;
}
treeface
  • 13,270
  • 4
  • 51
  • 57
  • Could you explain how the results are any different? For my tests, I get the exact same results. Thanks – Jeff Puckett Oct 24 '16 at 20:14
  • 2
    @JeffPuckettII sorry I didn't give a good explanation. I've updated the answer to explain how the accepted answer differs from `array_diff`. – treeface Oct 24 '16 at 23:14
  • 1
    Just checked the simple example at http://php.net/manual/en/function.array-diff.php and it works (behaves) as expected (EDIT) for simple arrays. But recursive does not work as it should. :( – cottton May 22 '17 at 16:25
  • it doesn't work if we have associative arrays with values as objects. For example, `$a = [ "foo"=>(object)["p1"=>1, "p2"=>2], "moo"=>(object)["p3"=>3]]`, `$b=[ "foo" => (object)["p4"=>4]]`. – Max Ivak Aug 03 '19 at 06:58
  • Sometimes considering [1, 2] and [2,1] different is not a wanted desire. Arrays are not sets where the order of elements is not important. PHP's array can represent many things. It is not easy to distinct an associative array (with keys being of any type) from a regular array (with integer keys). Regular array `[1,2]` is considered different from array `[2,1]`. That's why we have functions to add elements to the beginning or the end of array, not just a function to add element to array (set). If you want to treat array as a set then it would be another function to compare array as sets. – Max Ivak Aug 03 '19 at 07:04
7
function array_diff_assoc_recursive($array1, $array2)
{
    foreach($array1 as $key => $value){

        if(is_array($value)){
            if(!isset($array2[$key]))
            {
                $difference[$key] = $value;
            }
            elseif(!is_array($array2[$key]))
            {
                $difference[$key] = $value;
            }
            else
            {
                $new_diff = array_diff_assoc_recursive($value, $array2[$key]);
                if($new_diff != FALSE)
                {
                    $difference[$key] = $new_diff;
                }
            }
        }
        elseif((!isset($array2[$key]) || $array2[$key] != $value) && !($array2[$key]===null && $value===null))
        {
            $difference[$key] = $value;
        }
    }
    return !isset($difference) ? 0 : $difference;
}

Example:

$a = array(
    "product_a" => array(
        'description'=>'Product A',
        'color'=>'Red',
        'quantity'=>'5',
        'serial'=>array(1,2,3)
    ),
    "product_b" => array(
        'description'=>'Product B'
    )
);

$b = array(
    "product_a" => array(
        'description'=>'Product A',
        'color'=>'Blue',
        'quantity'=>'5',
        'serial'=>array(1,2,5)
    ),
    "product_b" => array(
        'description'=>'Product B'
    )
);

Output:

array_diff_assoc_recursive($a,$b);

Array
(
    [product_a] => Array
        (
            [color] => Red
            [serial] => Array
                (
                    [2] => 3
                )    
        )    
)
JR_
  • 71
  • 2
  • 4
2

Try this code:

function arrayDiffRecursive($firstArray, $secondArray, $reverseKey = false)
{
    $oldKey = 'old';
    $newKey = 'new';
    if ($reverseKey) {
        $oldKey = 'new';
        $newKey = 'old';
    }
    $difference = [];
    foreach ($firstArray as $firstKey => $firstValue) {
        if (is_array($firstValue)) {
            if (!array_key_exists($firstKey, $secondArray) || !is_array($secondArray[$firstKey])) {
                $difference[$oldKey][$firstKey] = $firstValue;
                $difference[$newKey][$firstKey] = '';
            } else {
                $newDiff = arrayDiffRecursive($firstValue, $secondArray[$firstKey], $reverseKey);
                if (!empty($newDiff)) {
                    $difference[$oldKey][$firstKey] = $newDiff[$oldKey];
                    $difference[$newKey][$firstKey] = $newDiff[$newKey];
                }
            }
        } else {
            if (!array_key_exists($firstKey, $secondArray) || $secondArray[$firstKey] != $firstValue) {
                $difference[$oldKey][$firstKey] = $firstValue;
                $difference[$newKey][$firstKey] = $secondArray[$firstKey];
            }
        }
    }
    return $difference;
}

$differences = array_replace_recursive(
    arrayDiffRecursive($firstArray, $secondArray),
    arrayDiffRecursive($secondArray, $firstArray, true)
);
var_dump($differences);
M Rostami
  • 4,035
  • 1
  • 35
  • 39
0

The answer by Mohamad is working good, except that it needs change on the line:

$difference[$newKey][$firstKey] = $secondArray[$firstKey];

with:

$difference[$newKey][$firstKey] = array_key_exists($firstKey, $secondArray) ? $secondArray[$firstKey] : null;

or, if you are using Laravel, with:

$difference[$newKey][$firstKey] = array_get($secondArray, $firstKey);

Otherwise, you will get errors like

PHP error: Undefined index: some_key

when a some_key exists in $secondArray but not in $firstArray

jfeid
  • 71
  • 1
  • 1
0

The function arrayRecursiveDiff() in the provided PHP code helps to compare associative arrays recursively. It iterates over each element in the first array, comparing it to the corresponding element in the second array. If an element in the first array is also an array, the function recursively checks for differences in these sub-arrays. The function collects all unique elements from the first array not present in the second one (or differing), including those within nested arrays, and returns them as an associative array. This is effectively a recursive version of the array_diff() function which works on associative arrays and also supports multi-dimensional arrays.

function arrayRecursiveDiff(array $array1, array $array2): array
{
    $aReturn = [];

    foreach ($array1 as $index => $value) {
        if (array_key_exists($index, $array2)) {
            if (is_array($value) && is_array($array2[$index])) {
                $recursiveDiff = arrayRecursiveDiff($value, $array2[$index]);
                if ($recursiveDiff !== []) {
                    $aReturn[$index] = $recursiveDiff;
                }
            } elseif ($value !== $array2[$index]) {
                $aReturn[$index] = $value;
            }
        } else {
            $aReturn[$index] = $value;
        }
    }

    return $aReturn;
}

$array1 = ['test' => 'test', 'test2' => 'test2', 'test3' => ['test4' => 'test4', 'test5' => 'test5']];
$array2 = ['test' => 'test', 'test2' => 'test2', 'test3' => ['test4' => 'test4', 'test5' => ['test6' => 'test6', 'test7' => 'test7']]];

print_r(arrayRecursiveDiff($array2,$array1));
Lukáš Kříž
  • 630
  • 7
  • 5