From 95a468cebb450ed779db90ca8c5805ffa2c93370 Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Thu, 31 Dec 2009 17:39:36 +0200 Subject: [PATCH] Add equality test for JSON values --- doc/apiref.rst | 40 ++++++++ src/jansson.h | 5 + src/value.c | 93 +++++++++++++++++ test/.gitignore | 1 + test/suites/api/Makefile.am | 1 + test/suites/api/check-exports | 1 + test/suites/api/test_equal.c | 187 ++++++++++++++++++++++++++++++++++ 7 files changed, 328 insertions(+) create mode 100644 test/suites/api/test_equal.c diff --git a/doc/apiref.rst b/doc/apiref.rst index 1ca7c2b..bf22466 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -673,3 +673,43 @@ The following functions perform the actual JSON decoding. object it contains, or *NULL* on error, in which case *error* is filled with information about the error. See above for discussion on the *error* parameter. + + +Equality +======== + +Testing for equality of two JSON values cannot, in general, be +achieved using the ``==`` operator. Equality in the terms of the +``==`` operator states that the two :ctype:`json_t` pointers point to +exactly the same JSON value. However, two JSON values can be equal not +only if they are exactly the same value, but also if they have equal +"contents": + +* Two integer or real values are equal if their contained numeric + values are equal. An integer value is never equal to a real value, + though. + +* Two strings are equal if their contained UTF-8 strings are equal. + +* Two arrays are equal if they have the same number of elements and + each element in the first array is equal to the corresponding + element in the second array. + +* Two objects are equal if they have exactly the same keys and the + value for each key in the first object is equal to the value of the + corresponding key in the second object. + +* Two true, false or null values have no "contents", so they are equal + if their types are equal. (Because these values are singletons, + their equality can actually be tested with ``==``.) + +The following function can be used to test whether two JSON values are +equal. + +.. cfunction:: int json_equal(json_t *value1, json_t *value2) + + Returns 1 if *value1* and *value2* are equal, as defined above. + Returns 0 if they are inequal or one or both of the pointers are + *NULL*. + + .. versionadded:: 1.2 diff --git a/src/jansson.h b/src/jansson.h index 25906dd..b1b5c4b 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -137,6 +137,11 @@ int json_integer_set(json_t *integer, int value); int json_real_set(json_t *real, double value); +/* equality */ + +int json_equal(json_t *value1, json_t *value2); + + /* loading, printing */ #define JSON_ERROR_TEXT_LENGTH 160 diff --git a/src/value.c b/src/value.c index 0ab8232..5777149 100644 --- a/src/value.c +++ b/src/value.c @@ -217,6 +217,32 @@ json_t *json_object_iter_value(void *iter) return (json_t *)hashtable_iter_value(iter); } +static int json_object_equal(json_t *object1, json_t *object2) +{ + void *iter; + + if(json_object_size(object1) != json_object_size(object2)) + return 0; + + iter = json_object_iter(object1); + while(iter) + { + const char *key; + json_t *value1, *value2; + + key = json_object_iter_key(iter); + value1 = json_object_iter_value(iter); + value2 = json_object_get(object2, key); + + if(!json_equal(value1, value2)) + return 0; + + iter = json_object_iter_next(object1, iter); + } + + return 1; +} + /*** array ***/ @@ -463,6 +489,28 @@ int json_array_extend(json_t *json, json_t *other_json) return 0; } +static int json_array_equal(json_t *array1, json_t *array2) +{ + unsigned int i, size; + + size = json_array_size(array1); + if(size != json_array_size(array2)) + return 0; + + for(i = 0; i < size; i++) + { + json_t *value1, *value2; + + value1 = json_array_get(array1, i); + value2 = json_array_get(array2, i); + + if(!json_equal(value1, value2)) + return 0; + } + + return 1; +} + /*** string ***/ @@ -533,6 +581,10 @@ static void json_delete_string(json_string_t *string) free(string); } +static int json_string_equal(json_t *string1, json_t *string2) +{ + return strcmp(json_string_value(string1), json_string_value(string2)) == 0; +} /*** integer ***/ @@ -570,6 +622,10 @@ static void json_delete_integer(json_integer_t *integer) free(integer); } +static int json_integer_equal(json_t *integer1, json_t *integer2) +{ + return json_integer_value(integer1) == json_integer_value(integer2); +} /*** real ***/ @@ -607,6 +663,10 @@ static void json_delete_real(json_real_t *real) free(real); } +static int json_real_equal(json_t *real1, json_t *real2) +{ + return json_real_value(real1) == json_real_value(real2); +} /*** number ***/ @@ -674,3 +734,36 @@ void json_delete(json_t *json) /* json_delete is not called for true, false or null */ } + + +/*** equality ***/ + +int json_equal(json_t *json1, json_t *json2) +{ + if(!json1 || !json2) + return 0; + + if(json_typeof(json1) != json_typeof(json2)) + return 0; + + /* this covers true, false and null as they are singletons */ + if(json1 == json2) + return 1; + + if(json_is_object(json1)) + return json_object_equal(json1, json2); + + if(json_is_array(json1)) + return json_array_equal(json1, json2); + + if(json_is_string(json1)) + return json_string_equal(json1, json2); + + if(json_is_integer(json1)) + return json_integer_equal(json1, json2); + + if(json_is_real(json1)) + return json_real_equal(json1, json2); + + return 0; +} diff --git a/test/.gitignore b/test/.gitignore index 2cc5d6d..c5329b2 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,6 +1,7 @@ logs bin/json_process suites/api/test_array +suites/api/test_equal suites/api/test_load suites/api/test_number suites/api/test_object diff --git a/test/suites/api/Makefile.am b/test/suites/api/Makefile.am index a2829e1..b627da5 100644 --- a/test/suites/api/Makefile.am +++ b/test/suites/api/Makefile.am @@ -1,5 +1,6 @@ check_PROGRAMS = \ test_array \ + test_equal \ test_load \ test_simple \ test_number \ diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index d3bbe2f..5f25218 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -48,6 +48,7 @@ json_dump_file json_loads json_loadf json_load_file +json_equal EOF # The list of functions are not exported in the library because they diff --git a/test/suites/api/test_equal.c b/test/suites/api/test_equal.c new file mode 100644 index 0000000..e056750 --- /dev/null +++ b/test/suites/api/test_equal.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2009 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include "util.h" + +static void test_equal_simple() +{ + json_t *value1, *value2; + + if(json_equal(NULL, NULL)) + fail("json_equal fails for two NULLs"); + + value1 = json_true(); + if(json_equal(value1, NULL) || json_equal(NULL, value1)) + fail("json_equal fails for NULL"); + + /* this covers true, false and null as they are singletons */ + if(!json_equal(value1, value1)) + fail("identical objects are not equal"); + json_decref(value1); + + /* integer */ + value1 = json_integer(1); + value2 = json_integer(1); + if(!value1 || !value2) + fail("unable to create integers"); + if(!json_equal(value1, value2)) + fail("json_equal fails for two equal integers"); + json_decref(value2); + + value2 = json_integer(2); + if(!value2) + fail("unable to create an integer"); + if(json_equal(value1, value2)) + fail("json_equal fails for two inequal integers"); + + json_decref(value1); + json_decref(value2); + + /* real */ + value1 = json_real(1.2); + value2 = json_real(1.2); + if(!value1 || !value2) + fail("unable to create reals"); + if(!json_equal(value1, value2)) + fail("json_equal fails for two equal reals"); + json_decref(value2); + + value2 = json_real(3.141592); + if(!value2) + fail("unable to create an real"); + if(json_equal(value1, value2)) + fail("json_equal fails for two inequal reals"); + + json_decref(value1); + json_decref(value2); + + /* string */ + value1 = json_string("foo"); + value2 = json_string("foo"); + if(!value1 || !value2) + fail("unable to create strings"); + if(!json_equal(value1, value2)) + fail("json_equal fails for two equal strings"); + json_decref(value2); + + value2 = json_string("bar"); + if(!value2) + fail("unable to create an string"); + if(json_equal(value1, value2)) + fail("json_equal fails for two inequal strings"); + + json_decref(value1); + json_decref(value2); +} + +static void test_equal_array() +{ + json_t *array1, *array2; + + array1 = json_array(); + array2 = json_array(); + if(!array1 || !array2) + fail("unable to create arrays"); + + if(!json_equal(array1, array2)) + fail("json_equal fails for two empty arrays"); + + json_array_append_new(array1, json_integer(1)); + json_array_append_new(array2, json_integer(1)); + json_array_append_new(array1, json_string("foo")); + json_array_append_new(array2, json_string("foo")); + json_array_append_new(array1, json_integer(2)); + json_array_append_new(array2, json_integer(2)); + if(!json_equal(array1, array2)) + fail("json_equal fails for two equal arrays"); + + json_array_remove(array2, 2); + if(json_equal(array1, array2)) + fail("json_equal fails for two inequal arrays"); + + json_array_append_new(array2, json_integer(3)); + if(json_equal(array1, array2)) + fail("json_equal fails for two inequal arrays"); + + json_decref(array1); + json_decref(array2); +} + +static void test_equal_object() +{ + json_t *object1, *object2; + + object1 = json_object(); + object2 = json_object(); + if(!object1 || !object2) + fail("unable to create objects"); + + if(!json_equal(object1, object2)) + fail("json_equal fails for two empty objects"); + + json_object_set_new(object1, "a", json_integer(1)); + json_object_set_new(object2, "a", json_integer(1)); + json_object_set_new(object1, "b", json_string("foo")); + json_object_set_new(object2, "b", json_string("foo")); + json_object_set_new(object1, "c", json_integer(2)); + json_object_set_new(object2, "c", json_integer(2)); + if(!json_equal(object1, object2)) + fail("json_equal fails for two equal objects"); + + json_object_del(object2, "c"); + if(json_equal(object1, object2)) + fail("json_equal fails for two inequal objects"); + + json_object_set(object2, "c", json_integer(3)); + if(json_equal(object1, object2)) + fail("json_equal fails for two inequal objects"); + + json_object_del(object2, "c"); + json_object_set(object2, "d", json_integer(2)); + if(json_equal(object1, object2)) + fail("json_equal fails for two inequal objects"); + + json_decref(object1); + json_decref(object2); +} + +static void test_equal_complex() +{ + json_t *value1, *value2; + + const char *complex_json = +"{" +" \"integer\": 1, " +" \"real\": 3.141592, " +" \"string\": \"foobar\", " +" \"true\": true, " +" \"object\": {" +" \"array-in-object\": [1,true,\"foo\",{}]," +" \"object-in-object\": {\"foo\": \"bar\"}" +" }," +" \"array\": [\"foo\", false, null, 1.234]" +"}"; + + value1 = json_loads(complex_json, NULL); + value2 = json_loads(complex_json, NULL); + if(!value1 || !value2) + fail("unable to parse JSON"); + if(!json_equal(value1, value2)) + fail("json_equal fails for two inequal strings"); + + /* TODO: There's no negative test case here */ +} + +int main() +{ + test_equal_simple(); + test_equal_array(); + test_equal_object(); + test_equal_complex(); + return 0; +}