phpunit icon indicating copy to clipboard operation
phpunit copied to clipboard

JSON assertions should treat objects as unordered

Open Firehed opened this issue 4 years ago • 0 comments

Q A
PHPUnit version 9.5.4
PHP version 9.0.1
Installation Method Composer

Summary

According to the JSON spec outlined at json.org, an object (dictionaries) "is an unordered set of name/value pairs". PHPUnit's JSON comparsion assertions (assertJsonStringEqualsJsonString and friends) appear to enforce an ordered set.

The internals suggest this is supposed to be handled already. I tried adding SORT_STRING as a flag to ksort here and it seemed to fix things, but I hardly did exhaustive testing.

Somewhat related to #4584, but more in the sense that it's impossible to maintain a native PHP array that 1:1 matches certain JSON due to numeric key conversion. Getting that all correct is tragically difficult.

Current behavior

good compare
Failed asserting that '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}' matches JSON string "{
    "0": null,
    "a": {},
    "b": [],
    "c": "1",
    "d": 1,
    "e": -1,
    "f": [1,2],
    "g": [2,1],
    "h": {"0":"0","1":"1","2":"2"}
}".
--- Expected
+++ Actual
@@ @@
 {
-    "0": null,
     "a": {},
     "b": [],
-    "c": "1",
     "d": 1,
     "e": -1,
+    "0": null,
+    "c": "1",
     "f": [
         1,
         2

How to reproduce

    public function testJson(): void
    {
        $expected = <<<'JSON'
        {
            "0": null,
            "a": {},
            "b": [],
            "c": "1",
            "d": 1,
            "e": -1,
            "f": [1,2],
            "g": [2,1],
            "h": {"0":"0","1":"1","2":"2"}
        }
        JSON;

        $this->assertJsonStringEqualsJsonString(
            $expected,
            '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}',
            'good compare',
        );
        try {
            $this->assertJsonStringEqualsJsonString(
                $expected,
                '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[2,1],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}',
                'bad!',
            );
            $this->fail('should fail: f is transposed');
        } catch (\Throwable $e) {}
        try {
            $this->assertJsonStringEqualsJsonString(
                $expected,
                '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":[0,1,2],"g":[2,1]}',
                'bad!',
            );
            $this->fail('should fail: h changed from obj to array (intvals)');
        } catch (\Throwable $e) {}
        //#4584?
        try {
            $this->assertJsonStringEqualsJsonString(
                $expected,
                '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":["0","1","2"],"g":[2,1]}',
                'bad!',
            );
            $this->fail('should fail: h changed from obj to array (strings)');
        } catch (\Throwable $e) {}
    }

Expected behavior

Above test case passes.

Firehed avatar May 19 '21 20:05 Firehed