From 38f421a2e7f4453bdc7f206681dbcbfb1b00de4c Mon Sep 17 00:00:00 2001 From: Eric Haszlakiewicz Date: Sun, 2 Sep 2012 15:21:56 -0500 Subject: [PATCH] Add a json_set_serializer() function to allow the string output of a json_object to be customized. --- .gitignore | 1 + json_object.c | 72 ++++++++++++++++++++++++++++-- json_object.h | 45 +++++++++++++++++++ json_object_private.h | 10 ++--- tests/Makefile.am | 4 ++ tests/test_set_serializer.c | 71 +++++++++++++++++++++++++++++ tests/test_set_serializer.expected | 9 ++++ tests/test_set_serializer.test | 12 +++++ 8 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 tests/test_set_serializer.c create mode 100644 tests/test_set_serializer.expected create mode 100755 tests/test_set_serializer.test diff --git a/.gitignore b/.gitignore index a5034a99..843fb3c8 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ /tests/test_cast /tests/test_null /tests/test_printbuf +/tests/test_set_serializer /Debug /Release *.lo diff --git a/json_object.c b/json_object.c index 8dd13b0d..d11efc52 100644 --- a/json_object.c +++ b/json_object.c @@ -46,6 +46,13 @@ const char *json_hex_chars = "0123456789abcdefABCDEF"; static void json_object_generic_delete(struct json_object* jso); static struct json_object* json_object_new(enum json_type o_type); +static json_object_to_json_string_fn json_object_object_to_json_string; +static json_object_to_json_string_fn json_object_boolean_to_json_string; +static json_object_to_json_string_fn json_object_int_to_json_string; +static json_object_to_json_string_fn json_object_double_to_json_string; +static json_object_to_json_string_fn json_object_string_to_json_string; +static json_object_to_json_string_fn json_object_array_to_json_string; + /* ref count debugging */ @@ -134,10 +141,16 @@ extern struct json_object* json_object_get(struct json_object *jso) extern void json_object_put(struct json_object *jso) { - if(jso) { - jso->_ref_count--; - if(!jso->_ref_count) jso->_delete(jso); - } + if(jso) + { + jso->_ref_count--; + if(!jso->_ref_count) + { + if (jso->_user_delete) + jso->_user_delete(jso, jso->_userdata); + jso->_delete(jso); + } + } } @@ -187,6 +200,57 @@ enum json_type json_object_get_type(struct json_object *jso) return jso->o_type; } +/* set a custom conversion to string */ + +void json_object_set_serializer(json_object *jso, + json_object_to_json_string_fn to_string_func, + void *userdata, + json_object_delete_fn *user_delete) +{ + // First, clean up any previously existing user info + if (jso->_user_delete) + { + jso->_user_delete(jso, jso->_userdata); + } + jso->_userdata = NULL; + jso->_user_delete = NULL; + + if (to_string_func == NULL) + { + // Reset to the standard serialization function + switch(jso->o_type) + { + case json_type_null: + jso->_to_json_string = NULL; + break; + case json_type_boolean: + jso->_to_json_string = &json_object_boolean_to_json_string; + break; + case json_type_double: + jso->_to_json_string = &json_object_double_to_json_string; + break; + case json_type_int: + jso->_to_json_string = &json_object_int_to_json_string; + break; + case json_type_object: + jso->_to_json_string = &json_object_object_to_json_string; + break; + case json_type_array: + jso->_to_json_string = &json_object_array_to_json_string; + break; + case json_type_string: + jso->_to_json_string = &json_object_string_to_json_string; + break; + } + return; + } + + jso->_to_json_string = to_string_func; + jso->_userdata = userdata; + jso->_user_delete = user_delete; +} + + /* extended conversion to string */ const char* json_object_to_json_string_ext(struct json_object *jso, int flags) diff --git a/json_object.h b/json_object.h index 6520a9a7..f264629c 100644 --- a/json_object.h +++ b/json_object.h @@ -70,6 +70,19 @@ typedef struct json_object json_object; typedef struct json_object_iter json_object_iter; typedef struct json_tokener json_tokener; +/** + * Type of custom user delete functions. See json_object_set_serializer. + */ +typedef void (json_object_delete_fn)(struct json_object *jso, void *userdata); + +/** + * Type of a custom serialization function. See json_object_set_serializer. + */ +typedef int (json_object_to_json_string_fn)(struct json_object *jso, + struct printbuf *pb, + int level, + int flags); + /* supported object types */ typedef enum json_type { @@ -149,6 +162,38 @@ extern const char* json_object_to_json_string(struct json_object *obj); extern const char* json_object_to_json_string_ext(struct json_object *obj, int flags); +/** + * Set a custom serialization function to be used when this particular object + * is converted to a string by json_object_to_json_string. + * + * If a custom serializer is already set on this object, any existing + * user_delete function is called before the new one is set. + * + * If to_string_func is NULL, the other parameters are ignored + * and the default behaviour is reset. + * + * The userdata parameter is optional and may be passed as NULL. If provided, + * it is passed to to_string_func as-is. This parameter may be NULL even + * if user_delete is non-NULL. + * + * The user_delete parameter is optional and may be passed as NULL, even if + * the userdata parameter is non-NULL. It will be called just before the + * json_object is deleted, after it's reference count goes to zero + * (see json_object_put()). + * If this is not provided, it is up to the caller to free the userdata at + * an appropriate time. (i.e. after the json_object is deleted) + * + * @param jso the object to customize + * @param to_string_func the custom serialization function + * @param userdata an optional opaque cookie + * @param user_delete an optional function from freeing userdata + */ +void json_object_set_serializer(json_object *jso, + json_object_to_json_string_fn to_string_func, + void *userdata, + json_object_delete_fn *user_delete); + + /* object type methods */ diff --git a/json_object_private.h b/json_object_private.h index 597332b9..5ed791b5 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -16,16 +16,12 @@ extern "C" { #endif -typedef void (json_object_delete_fn)(struct json_object *o); -typedef int (json_object_to_json_string_fn)(struct json_object *o, - struct printbuf *pb, - int level, - int flags); +typedef void (json_object_private_delete_fn)(struct json_object *o); struct json_object { enum json_type o_type; - json_object_delete_fn *_delete; + json_object_private_delete_fn *_delete; json_object_to_json_string_fn *_to_json_string; int _ref_count; struct printbuf *_pb; @@ -40,6 +36,8 @@ struct json_object int len; } c_string; } o; + json_object_delete_fn *_user_delete; + void *_userdata; }; #ifdef __cplusplus diff --git a/tests/Makefile.am b/tests/Makefile.am index 635ce558..8057acdf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -42,6 +42,10 @@ TESTS+= test_printbuf.test check_PROGRAMS+=test_printbuf test_printbuf_LDADD = $(LIBJSON_LA) +TESTS+= test_set_serializer.test +check_PROGRAMS += test_set_serializer +test_set_serializer_LDADD = $(LIBJSON_LA) + EXTRA_DIST= EXTRA_DIST += $(TESTS) diff --git a/tests/test_set_serializer.c b/tests/test_set_serializer.c new file mode 100644 index 00000000..ae0121bc --- /dev/null +++ b/tests/test_set_serializer.c @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "json.h" +#include "printbuf.h" + +struct myinfo { + int value; +}; + +static int freeit_was_called = 0; +static void freeit(json_object *jso, void *userdata) +{ + struct myinfo *info = userdata; + printf("freeit, value=%d\n", info->value); + // Don't actually free anything here, the userdata is stack allocated. + freeit_was_called = 1; +} +static int custom_serializer(struct json_object *o, + struct printbuf *pb, + int level, + int flags) +{ + sprintbuf(pb, "Custom Output"); + return 0; +} + +int main(int argc, char **argv) +{ + json_object *my_object; + + MC_SET_DEBUG(1); + + printf("Test setting, then resetting a custom serializer:\n"); + my_object = json_object_new_object(); + json_object_object_add(my_object, "abc", json_object_new_int(12)); + json_object_object_add(my_object, "foo", json_object_new_string("bar")); + + printf("my_object.to_string(standard)=%s\n", json_object_to_json_string(my_object)); + + struct myinfo userdata = { .value = 123 }; + json_object_set_serializer(my_object, custom_serializer, &userdata, freeit); + + printf("my_object.to_string(custom serializer)=%s\n", json_object_to_json_string(my_object)); + + printf("Next line of output should be from the custom freeit function:\n"); + freeit_was_called = 0; + json_object_set_serializer(my_object, NULL, NULL, NULL); + assert(freeit_was_called); + + printf("my_object.to_string(standard)=%s\n", json_object_to_json_string(my_object)); + + json_object_put(my_object); + + // ============================================ + + my_object = json_object_new_object(); + printf("Check that the custom serializer isn't free'd until the last json_object_put:\n"); + json_object_set_serializer(my_object, custom_serializer, &userdata, freeit); + json_object_get(my_object); + json_object_put(my_object); + printf("my_object.to_string(custom serializer)=%s\n", json_object_to_json_string(my_object)); + printf("Next line of output should be from the custom freeit function:\n"); + + freeit_was_called = 0; + json_object_put(my_object); + assert(freeit_was_called); + + return 0; +} diff --git a/tests/test_set_serializer.expected b/tests/test_set_serializer.expected new file mode 100644 index 00000000..b91a0814 --- /dev/null +++ b/tests/test_set_serializer.expected @@ -0,0 +1,9 @@ +my_object.to_string(standard)={ "abc": 12, "foo": "bar" } +my_object.to_string(custom serializer)=Custom Output +Next line of output should be from the custom freeit function: +freeit, value=123 +my_object.to_string(standard)={ "abc": 12, "foo": "bar" } +Check that the custom serializer isn't free'd until the last json_object_put: +my_object.to_string(custom serializer)=Custom Output +Next line of output should be from the custom freeit function: +freeit, value=123 diff --git a/tests/test_set_serializer.test b/tests/test_set_serializer.test new file mode 100755 index 00000000..728dfedf --- /dev/null +++ b/tests/test_set_serializer.test @@ -0,0 +1,12 @@ +#!/bin/sh + +# Common definitions +if test -z "$srcdir"; then + srcdir="${0%/*}" + test "$srcdir" = "$0" && srcdir=. + test -z "$srcdir" && srcdir=. +fi +. "$srcdir/test-defs.sh" + +run_output_test test_set_serializer +exit $? -- 2.47.2