Add equality test for JSON values
authorPetri Lehtinen <petri@digip.org>
Thu, 31 Dec 2009 15:39:36 +0000 (17:39 +0200)
committerPetri Lehtinen <petri@digip.org>
Thu, 31 Dec 2009 16:50:36 +0000 (18:50 +0200)
doc/apiref.rst
src/jansson.h
src/value.c
test/.gitignore
test/suites/api/Makefile.am
test/suites/api/check-exports
test/suites/api/test_equal.c [new file with mode: 0644]

index 1ca7c2b..bf22466 100644 (file)
@@ -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.
    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
index 25906dd..b1b5c4b 100644 (file)
@@ -137,6 +137,11 @@ int json_integer_set(json_t *integer, int value);
 int json_real_set(json_t *real, double 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
 /* loading, printing */
 
 #define JSON_ERROR_TEXT_LENGTH  160
index 0ab8232..5777149 100644 (file)
@@ -217,6 +217,32 @@ json_t *json_object_iter_value(void *iter)
     return (json_t *)hashtable_iter_value(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 ***/
 
 
 /*** array ***/
 
@@ -463,6 +489,28 @@ int json_array_extend(json_t *json, json_t *other_json)
     return 0;
 }
 
     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 ***/
 
 
 /*** string ***/
 
@@ -533,6 +581,10 @@ static void json_delete_string(json_string_t *string)
     free(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 ***/
 
 
 /*** integer ***/
 
@@ -570,6 +622,10 @@ static void json_delete_integer(json_integer_t *integer)
     free(integer);
 }
 
     free(integer);
 }
 
+static int json_integer_equal(json_t *integer1, json_t *integer2)
+{
+    return json_integer_value(integer1) == json_integer_value(integer2);
+}
 
 /*** real ***/
 
 
 /*** real ***/
 
@@ -607,6 +663,10 @@ static void json_delete_real(json_real_t *real)
     free(real);
 }
 
     free(real);
 }
 
+static int json_real_equal(json_t *real1, json_t *real2)
+{
+    return json_real_value(real1) == json_real_value(real2);
+}
 
 /*** number ***/
 
 
 /*** number ***/
 
@@ -674,3 +734,36 @@ void json_delete(json_t *json)
 
     /* json_delete is not called for true, false or null */
 }
 
     /* 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;
+}
index 2cc5d6d..c5329b2 100644 (file)
@@ -1,6 +1,7 @@
 logs
 bin/json_process
 suites/api/test_array
 logs
 bin/json_process
 suites/api/test_array
+suites/api/test_equal
 suites/api/test_load
 suites/api/test_number
 suites/api/test_object
 suites/api/test_load
 suites/api/test_number
 suites/api/test_object
index a2829e1..b627da5 100644 (file)
@@ -1,5 +1,6 @@
 check_PROGRAMS = \
        test_array \
 check_PROGRAMS = \
        test_array \
+       test_equal \
        test_load \
        test_simple \
        test_number \
        test_load \
        test_simple \
        test_number \
index d3bbe2f..5f25218 100755 (executable)
@@ -48,6 +48,7 @@ json_dump_file
 json_loads
 json_loadf
 json_load_file
 json_loads
 json_loadf
 json_load_file
+json_equal
 EOF
 
 # The list of functions are not exported in the library because they
 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 (file)
index 0000000..e056750
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2009 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <jansson.h>
+#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;
+}