7

I have two JSON objects and I would like to compare their structure. How can I do it?

Those object are being generated on-the-fly and depending on dynamic content. Which means that the objects are always different but most of the time have the same structure. I want to be able to catch the changes once they occur.

Example: These two objects should be considered as equal, because both have the same structure: index var and tags array.

{
    "index": 0,
    "tags": [
        "abc"
    ]
}
{
    "index": 1,
    "tags": [
        "xyz"
    ]
}

Thoughts?

Boarking
  • 95
  • 1
  • 2
  • 6
  • so you want to check if object 1 has the same fields as object 2? – ksbg Aug 17 '15 at 08:24
  • Yes, exactly. I tried to use RecursiveArrayIterator::hasChildren() in order to iterate over leaves only but this solution does not seem to me an elegant one. May be someone knows a better way? – Boarking Aug 17 '15 at 08:42
  • @Boarking, did you get it working? – vonUbisch Aug 17 '15 at 09:41
  • maybe useful? [compare object properties and show diff in PHP](http://stackoverflow.com/questions/5911067/compare-object-properties-and-show-diff-in-php) – Ryan Vincent Aug 17 '15 at 11:13

5 Answers5

13

## You can use this library TreeWalker php .##

TreeWalker is a simple and smal API in php
(I developed this library, i hope it helps you)

It offers two methods
1- Get json difference
2- Edit json value (Recursively)

this method will return the diference between json1 and json2

$struct1 = array("casa"=>1, "b"=>"5", "cafeina"=>array("ss"=>"ddd"), "oi"=>5);
$struct2 = array("casa"=>2, "cafeina"=>array("ss"=>"dddd"), "oi2"=>5);

//P.s
print_r($treeWalker->getdiff($struct1, $struct2))

{
    new: {
        b: "5",
        oi: 5
    },
    removed: {
        oi2: 5
    },
    edited: {
        casa: {
          oldvalue: 2,
          newvalue: 1
        },
        cafeina/ss: {
          oldvalue: "dddd",
          newvalue: "ddd"
        }
    },
    time: 0
}
Lucas Cordeiro
  • 213
  • 3
  • 8
1

It's a bit rough, but you get the picture;

$json = '[
        {
            "index": 0,
            "tags": [
                "abc"
            ]
        },
        {
            "index": 1,
            "tags": [
                "xyz"
            ]
        },
        {
            "foo": 2,
            "bar": [
                "xyz"
            ]
        }]';

$array = json_decode($json, true);
$default = array_keys($array[0]);

$error = false;
$errors = array();
foreach ($array as $index => $result):
    foreach ($default as $search):
        if (!isset($result[$search])):
            $error = true;
            $errors[] = "Property '{$search}' at entry '{$index}' not found. ";
        endif;
    endforeach;
endforeach;

if ($error):
    echo 'Objects are not the same. ';
    foreach ($errors as $message):
        echo $message;
    endforeach;
endif;

returns:

Objects are not the same. Property 'index' at entry '2' not found. Property 'tags' at entry '2' not found.

vonUbisch
  • 1,384
  • 17
  • 32
  • The problem here is with the default array. How can I generate it? All I have is two JSON objects. array_keys() is also not a good option because it is not recursive. My actual objects are very big and have many levels of nesting. I have to be able to compare everything. – Boarking Aug 17 '15 at 08:48
  • Is it not possible to expect a certain structure? As far as I know, to do this efficiently you will need to expect something. – vonUbisch Aug 17 '15 at 08:51
  • I have no idea of what I am expecting. All I know is that I have two JSON objects which likely the same but I have to be sure before I proceed. – Boarking Aug 17 '15 at 08:55
1

You can try to use package https://github.com/coduo/php-matcher

Example: These two objects should be considered as equal, because both have the same structure: index var and tags array.

You can create a "php-matcher pattern" like this:

{
    "index": "@integer@",
    "tags": "@array@.repeat(\"@string@\")"
}

Then you match your JSONs against this pattern. If you have 2 JSONs and both match this pattern then it means that they are "equal" according to your definition of equality above.

Please see results in "php-matcher sandbox" for the example JSONs you gave:

Example 1 in sandbox

Example 2 in sandbox

Additionally you can use package https://github.com/sebastianbergmann/diff (which you should already have if you have phpunit) to generate a diff when the pattern doesnt match the value.

For example:

use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;

...
    $valueToCheck = '{
        "foo": 0,
        "bar": {"one": 1, "two": "2"}
    }';
    $expectedValuePattern = '{
        "foo": "@integer@",
        "bar": {"one": 1, "two": 2}
    }';

    if (!$matcher->match($valueToCheck, $expectedValuePattern)) {
        $differ = new Differ(
            new UnifiedDiffOutputBuilder(
                "Json value is not matching expected format:\n",
                true
            )
        );
        $diffOutput = $differ->diff(
            \json_encode(\json_decode($expectedValuePattern, true), JSON_PRETTY_PRINT),
            \json_encode(\json_decode($valueToCheck, true), JSON_PRETTY_PRINT)
        );

        var_dump(
            $diffOutput
            . "\n".$matcher->getError()."\n"
        );
    } else {
        var_dump('OK');
    }

it will print:

Json value is not matching expected format:
@@ -1,7 +1,7 @@
 {
-    "foo": "@integer@",
+    "foo": 0,
     "bar": {
         "one": 1,
-        "two": 2
+        "two": "2"
     }
 }

That message with diff is especially helpfull for bigger JSON's to quickly see which element is not matching.

See more ways of usage in README of that package - especially:

https://github.com/coduo/php-matcher#json-matching

https://github.com/coduo/php-matcher#json-matching-with-unbounded-arrays-and-objects

This package is very good to use in automatic tests (for example: phpunit) to assert if JSON from API responses is correct etc - considering that in integration tests there are often many id's, uuid's, datetime's etc which change on each test execution - like database generated id's etc.

I hope it helps :)

domis86
  • 1,227
  • 11
  • 9
0

You mean by structure, like model array like:

array ( 'index' => int, 'tags' =>  array() )

If that's what you are trying to get, try this...

$arr1 = array (
    array (
        'index' => 0,
        'tags' =>  ['abc']
    ),
    array (
        'index' => 1,
        'tags' =>  ['xyz']
    ),
    array (
        'index' => 2,
        'tags' =>  ['xyz'],
        'boom' =>  'granade'
    ),
    array (
        'index' => 3,
        'tags' =>  'xyz'
    )
);

$holder = array();

$model  = array ('index' => 0, 'tags' => array());

for ($i = 0;$i < count($arr1); $i++)
{
    $holder = array_diff(array_merge_recursive($arr1[$i], $model), $model);

    if (!empty($holder))
    {
        echo "different structure<br>";
    }
    else
    {
        echo "same structure<br>";

        // for further validation
        /*
        $keys = array_keys($model);

        if (is_int($arr1[$i][$keys[0]]) && is_array($arr1[$i][$keys[1]]))
            echo "same structure<br>";
        else
            echo "different structure<br>";
        */
    }

}

Sample output:

same structure
same structure
different structure
different structure
0yeoj
  • 4,500
  • 3
  • 23
  • 41
-1

You can convert the json string to a php array then use the array_diff($arr1,$arr2) function to compare the newly created array with another array the result is an array containing the elements of the first array that doesn't exist in the other array

example :

<?php 
    $array1 = '{"name":"myname","age":"40"}';
    //convert the obtained stdclass object to an array
    $array1 = (array) json_decode($array1); 



    $array2 = array("name"=>"myname123","age"=>10);

    print_r($array2);

    $result_array = array_diff($array1,$array2);


    if(empty($result_array[0])){     
        echo "they have the same structure ";
    }



?>
ismnoiet
  • 4,129
  • 24
  • 30