From 9db34dc31a69312ec1f486e6273d1bf5c80f978d Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Thu, 31 Dec 2009 15:56:28 +0200 Subject: [PATCH] Add functions for shallow and deep copying JSON values --- doc/apiref.rst | 32 +++++ src/jansson.h | 6 + src/value.c | 155 ++++++++++++++++++++ test/.gitignore | 1 + test/suites/api/Makefile.am | 2 + test/suites/api/check-exports | 2 + test/suites/api/test_copy.c | 319 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 517 insertions(+) create mode 100644 test/suites/api/test_copy.c diff --git a/doc/apiref.rst b/doc/apiref.rst index bf22466..a2a0794 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -713,3 +713,35 @@ equal. *NULL*. .. versionadded:: 1.2 + + +Copying +======= + +Because of reference counting, passing JSON values around doesn't +require copying them. But sometimes a fresh copy of a JSON value is +needed. For example, if you need to modify an array, but still want to +use the original afterwards, you should take a copy of it first. + +Jansson supports two kinds of copying: shallow and deep. There is a +difference between these methods only for arrays and objects. Shallow +copying only copies the first level value (array or object) and uses +the same child values in the copied value. Deep copying makes a fresh +copy of the child values, too. Moreover, all the child values are deep +copied in a recursive fashion. + +.. cfunction:: json_t *json_copy(json_t *value) + + .. refcounting:: new + + Returns a shallow copy of *value*, or *NULL* on error. + + .. versionadded:: 1.2 + +.. cfunction:: json_t *json_deep_copy(json_t *value) + + .. refcounting:: new + + Returns a deep copy of *value*, or *NULL* on error. + + .. versionadded:: 1.2 diff --git a/src/jansson.h b/src/jansson.h index b1b5c4b..73f6ce0 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -142,6 +142,12 @@ int json_real_set(json_t *real, double value); int json_equal(json_t *value1, json_t *value2); +/* copying */ + +json_t *json_copy(json_t *value); +json_t *json_deep_copy(json_t *value); + + /* loading, printing */ #define JSON_ERROR_TEXT_LENGTH 160 diff --git a/src/value.c b/src/value.c index 5777149..2b97947 100644 --- a/src/value.c +++ b/src/value.c @@ -243,6 +243,56 @@ static int json_object_equal(json_t *object1, json_t *object2) return 1; } +static json_t *json_object_copy(json_t *object) +{ + json_t *result; + void *iter; + + result = json_object(); + if(!result) + return NULL; + + iter = json_object_iter(object); + while(iter) + { + const char *key; + json_t *value; + + key = json_object_iter_key(iter); + value = json_object_iter_value(iter); + json_object_set(result, key, value); + + iter = json_object_iter_next(object, iter); + } + + return result; +} + +static json_t *json_object_deep_copy(json_t *object) +{ + json_t *result; + void *iter; + + result = json_object(); + if(!result) + return NULL; + + iter = json_object_iter(object); + while(iter) + { + const char *key; + json_t *value; + + key = json_object_iter_key(iter); + value = json_object_iter_value(iter); + json_object_set(result, key, json_deep_copy(value)); + + iter = json_object_iter_next(object, iter); + } + + return result; +} + /*** array ***/ @@ -511,6 +561,35 @@ static int json_array_equal(json_t *array1, json_t *array2) return 1; } +static json_t *json_array_copy(json_t *array) +{ + json_t *result; + unsigned int i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append(result, json_array_get(array, i)); + + return result; +} + +static json_t *json_array_deep_copy(json_t *array) +{ + json_t *result; + unsigned int i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append(result, json_deep_copy(json_array_get(array, i))); + + return result; +} /*** string ***/ @@ -586,6 +665,12 @@ static int json_string_equal(json_t *string1, json_t *string2) return strcmp(json_string_value(string1), json_string_value(string2)) == 0; } +static json_t *json_string_copy(json_t *string) +{ + return json_string(json_string_value(string)); +} + + /*** integer ***/ json_t *json_integer(int value) @@ -627,6 +712,12 @@ static int json_integer_equal(json_t *integer1, json_t *integer2) return json_integer_value(integer1) == json_integer_value(integer2); } +static json_t *json_integer_copy(json_t *integer) +{ + return json_integer(json_integer_value(integer)); +} + + /*** real ***/ json_t *json_real(double value) @@ -668,6 +759,12 @@ static int json_real_equal(json_t *real1, json_t *real2) return json_real_value(real1) == json_real_value(real2); } +static json_t *json_real_copy(json_t *real) +{ + return json_real(json_real_value(real)); +} + + /*** number ***/ double json_number_value(const json_t *json) @@ -767,3 +864,61 @@ int json_equal(json_t *json1, json_t *json2) return 0; } + + +/*** copying ***/ + +json_t *json_copy(json_t *json) +{ + if(!json) + return NULL; + + if(json_is_object(json)) + return json_object_copy(json); + + if(json_is_array(json)) + return json_array_copy(json); + + if(json_is_string(json)) + return json_string_copy(json); + + if(json_is_integer(json)) + return json_integer_copy(json); + + if(json_is_real(json)) + return json_real_copy(json); + + if(json_is_true(json) || json_is_false(json) || json_is_null(json)) + return json; + + return NULL; +} + +json_t *json_deep_copy(json_t *json) +{ + if(!json) + return NULL; + + if(json_is_object(json)) + return json_object_deep_copy(json); + + if(json_is_array(json)) + return json_array_deep_copy(json); + + /* for the rest of the types, deep copying doesn't differ from + shallow copying */ + + if(json_is_string(json)) + return json_string_copy(json); + + if(json_is_integer(json)) + return json_integer_copy(json); + + if(json_is_real(json)) + return json_real_copy(json); + + if(json_is_true(json) || json_is_false(json) || json_is_null(json)) + return json; + + return NULL; +} diff --git a/test/.gitignore b/test/.gitignore index c5329b2..a960c50 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -2,6 +2,7 @@ logs bin/json_process suites/api/test_array suites/api/test_equal +suites/api/test_copy 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 b627da5..772f75a 100644 --- a/test/suites/api/Makefile.am +++ b/test/suites/api/Makefile.am @@ -1,12 +1,14 @@ check_PROGRAMS = \ test_array \ test_equal \ + test_copy \ test_load \ test_simple \ test_number \ test_object test_array_SOURCES = test_array.c util.h +test_copy_SOURCES = test_copy.c util.h test_load_SOURCES = test_load.c util.h test_simple_SOURCES = test_simple.c util.h test_number_SOURCES = test_number.c util.h diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index 5f25218..178abeb 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -49,6 +49,8 @@ json_loads json_loadf json_load_file json_equal +json_copy +json_deep_copy EOF # The list of functions are not exported in the library because they diff --git a/test/suites/api/test_copy.c b/test/suites/api/test_copy.c new file mode 100644 index 0000000..9452183 --- /dev/null +++ b/test/suites/api/test_copy.c @@ -0,0 +1,319 @@ +/* + * 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 +#include "util.h" + +static void test_copy_simple(void) +{ + json_t *value, *copy; + + if(json_copy(NULL)) + fail("copying NULL doesn't return NULL"); + + /* true */ + value = json_true(); + copy = json_copy(value); + if(value != copy) + fail("copying true failed"); + json_decref(value); + json_decref(copy); + + /* false */ + value = json_false(); + copy = json_copy(value); + if(value != copy) + fail("copying false failed"); + json_decref(value); + json_decref(copy); + + /* null */ + value = json_null(); + copy = json_copy(value); + if(value != copy) + fail("copying null failed"); + json_decref(value); + json_decref(copy); + + /* string */ + value = json_string("foo"); + if(!value) + fail("unable to create a string"); + copy = json_copy(value); + if(!copy) + fail("unable to copy a string"); + if(copy == value) + fail("copying a string doesn't copy"); + if(!json_equal(copy, value)) + fail("copying a string produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); + + /* integer */ + value = json_integer(543); + if(!value) + fail("unable to create an integer"); + copy = json_copy(value); + if(!copy) + fail("unable to copy an integer"); + if(copy == value) + fail("copying an integer doesn't copy"); + if(!json_equal(copy, value)) + fail("copying an integer produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); + + /* real */ + value = json_real(123e9); + if(!value) + fail("unable to create a real"); + copy = json_copy(value); + if(!copy) + fail("unable to copy a real"); + if(copy == value) + fail("copying a real doesn't copy"); + if(!json_equal(copy, value)) + fail("copying a real produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); +} + +static void test_deep_copy_simple(void) +{ + json_t *value, *copy; + + if(json_deep_copy(NULL)) + fail("deep copying NULL doesn't return NULL"); + + /* true */ + value = json_true(); + copy = json_deep_copy(value); + if(value != copy) + fail("deep copying true failed"); + json_decref(value); + json_decref(copy); + + /* false */ + value = json_false(); + copy = json_deep_copy(value); + if(value != copy) + fail("deep copying false failed"); + json_decref(value); + json_decref(copy); + + /* null */ + value = json_null(); + copy = json_deep_copy(value); + if(value != copy) + fail("deep copying null failed"); + json_decref(value); + json_decref(copy); + + /* string */ + value = json_string("foo"); + if(!value) + fail("unable to create a string"); + copy = json_deep_copy(value); + if(!copy) + fail("unable to deep copy a string"); + if(copy == value) + fail("deep copying a string doesn't copy"); + if(!json_equal(copy, value)) + fail("deep copying a string produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); + + /* integer */ + value = json_integer(543); + if(!value) + fail("unable to create an integer"); + copy = json_deep_copy(value); + if(!copy) + fail("unable to deep copy an integer"); + if(copy == value) + fail("deep copying an integer doesn't copy"); + if(!json_equal(copy, value)) + fail("deep copying an integer produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); + + /* real */ + value = json_real(123e9); + if(!value) + fail("unable to create a real"); + copy = json_deep_copy(value); + if(!copy) + fail("unable to deep copy a real"); + if(copy == value) + fail("deep copying a real doesn't copy"); + if(!json_equal(copy, value)) + fail("deep copying a real produces an inequal copy"); + if(value->refcount != 1 || copy->refcount != 1) + fail("invalid refcounts"); + json_decref(value); + json_decref(copy); +} + +static void test_copy_array(void) +{ + const char *json_array_text = "[1, \"foo\", 3.141592, {\"foo\": \"bar\"}]"; + + json_t *array, *copy; + unsigned int i; + + array = json_loads(json_array_text, NULL); + if(!array) + fail("unable to parse an array"); + + copy = json_copy(array); + if(!copy) + fail("unable to copy an array"); + if(copy == array) + fail("copying an array doesn't copy"); + if(!json_equal(copy, array)) + fail("copying an array produces an inequal copy"); + + for(i = 0; i < json_array_size(copy); i++) + { + if(json_array_get(array, i) != json_array_get(copy, i)) + fail("copying an array modifies its elements"); + } + + json_decref(array); + json_decref(copy); +} + +static void test_deep_copy_array(void) +{ + const char *json_array_text = "[1, \"foo\", 3.141592, {\"foo\": \"bar\"}]"; + + json_t *array, *copy; + unsigned int i; + + array = json_loads(json_array_text, NULL); + if(!array) + fail("unable to parse an array"); + + copy = json_deep_copy(array); + if(!copy) + fail("unable to deep copy an array"); + if(copy == array) + fail("deep copying an array doesn't copy"); + if(!json_equal(copy, array)) + fail("deep copying an array produces an inequal copy"); + + for(i = 0; i < json_array_size(copy); i++) + { + if(json_array_get(array, i) == json_array_get(copy, i)) + fail("deep copying an array doesn't copy its elements"); + } + + json_decref(array); + json_decref(copy); +} + +static void test_copy_object(void) +{ + const char *json_object_text = + "{\"foo\": \"bar\", \"a\": 1, \"b\": 3.141592, \"c\": [1,2,3,4]}"; + + json_t *object, *copy; + void *iter; + + object = json_loads(json_object_text, NULL); + if(!object) + fail("unable to parse an object"); + + copy = json_copy(object); + if(!copy) + fail("unable to copy an object"); + if(copy == object) + fail("copying an object doesn't copy"); + if(!json_equal(copy, object)) + fail("copying an object produces an inequal copy"); + + iter = json_object_iter(object); + 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(copy, key); + + if(value1 != value2) + fail("deep copying an object modifies its items"); + + iter = json_object_iter_next(object, iter); + } + + json_decref(object); + json_decref(copy); +} + +static void test_deep_copy_object(void) +{ + const char *json_object_text = + "{\"foo\": \"bar\", \"a\": 1, \"b\": 3.141592, \"c\": [1,2,3,4]}"; + + json_t *object, *copy; + void *iter; + + object = json_loads(json_object_text, NULL); + if(!object) + fail("unable to parse an object"); + + copy = json_deep_copy(object); + if(!copy) + fail("unable to deep copy an object"); + if(copy == object) + fail("deep copying an object doesn't copy"); + if(!json_equal(copy, object)) + fail("deep copying an object produces an inequal copy"); + + iter = json_object_iter(object); + 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(copy, key); + + if(value1 == value2) + fail("deep copying an object doesn't copy its items"); + + iter = json_object_iter_next(object, iter); + } + + json_decref(object); + json_decref(copy); +} + +int main() +{ + test_copy_simple(); + test_deep_copy_simple(); + test_copy_array(); + test_deep_copy_array(); + test_copy_object(); + test_deep_copy_object(); + return 0; +} -- 2.1.4