diff --git a/src/Makefile.am b/src/Makefile.am index 635176e..659dcb4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,7 +11,8 @@ libjansson_la_SOURCES = \ strbuffer.h \ utf.c \ utf.h \ - value.c + value.c \ + variadic.c libjansson_la_LDFLAGS = \ -export-symbols-regex '^json_' \ -version-info 3:0:3 diff --git a/src/jansson.h b/src/jansson.h index e463f81..0f1de8d 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -85,6 +85,14 @@ void json_decref(json_t *json) } +/* error reporting */ + +typedef struct json_error_t json_error_t; + +const char *json_error_msg(const json_error_t *error); +int json_error_line(const json_error_t *error); + + /* getters, setters, manipulation */ size_t json_object_size(const json_t *object); @@ -156,6 +164,8 @@ int json_string_set_nocheck(json_t *string, const char *value); int json_integer_set(json_t *integer, json_int_t value); int json_real_set(json_t *real, double value); +json_t *json_pack(json_error_t **error, const char *fmt, ...); +int json_unpack(json_t *root, json_error_t **error, const char *fmt, ...); /* equality */ @@ -168,14 +178,6 @@ json_t *json_copy(json_t *value); json_t *json_deep_copy(json_t *value); -/* error reporting */ - -typedef struct json_error_t json_error_t; - -const char *json_error_msg(const json_error_t *error); -int json_error_line(const json_error_t *error); - - /* loading, printing */ json_t *json_loads(const char *input, size_t flags, json_error_t **error); diff --git a/src/jansson_private.h b/src/jansson_private.h index e9102ba..2d2af62 100644 --- a/src/jansson_private.h +++ b/src/jansson_private.h @@ -63,4 +63,11 @@ typedef struct { const object_key_t *jsonp_object_iter_fullkey(void *iter); +#define JSON_ERROR_MSG_LENGTH 160 + +struct json_error_t { + char msg[JSON_ERROR_MSG_LENGTH]; + int line; +}; + #endif diff --git a/src/load.c b/src/load.c index a34fd0c..54bfab8 100644 --- a/src/load.c +++ b/src/load.c @@ -60,13 +60,6 @@ typedef struct { /*** error reporting ***/ -#define JSON_ERROR_MSG_LENGTH 160 - -struct json_error_t { - char msg[JSON_ERROR_MSG_LENGTH]; - int line; -}; - const char *json_error_msg(const json_error_t *error) { return error ? error->msg : NULL; diff --git a/src/variadic.c b/src/variadic.c new file mode 100644 index 0000000..765be83 --- /dev/null +++ b/src/variadic.c @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2009, 2010 Petri Lehtinen + * Copyright (c) 2010 Graeme Smecher + * + * 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 + +#include +#include "jansson_private.h" + +static void error_init(json_error_t **error) +{ + if(error) + *error = NULL; +} + +static void error_set(json_error_t **error, const int line, const char *msg, ...) +{ + va_list ap; + + if(!error || *error) + return; + + *error = calloc(1, sizeof(json_error_t)); + if(!*error) + return; + + va_start(ap, msg); + vsnprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, msg, ap); + va_end(ap); + + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + + (*error)->line = line; +} + +json_t *json_pack(json_error_t **error, const char *fmt, ...) { + int fmt_length = strlen(fmt); + va_list ap; + + /* Keep a stack of containers (lists and objects) */ + int depth = 0; + json_t **stack = NULL; + + /* Keep a list of objects we create in case of error */ + int free_count = 0; + json_t **free_list = NULL; + + json_t *cur = NULL; /* Current container */ + json_t *root = NULL; /* root object */ + json_t *obj = NULL; + + char *key = NULL; /* Current key in an object */ + char *s; + + int line = 1; + + /* Allocation provisioned for worst case */ + stack = calloc(fmt_length, sizeof(json_t *)); + free_list = calloc(fmt_length, sizeof(json_t *)); + + error_init(error); + + if(!stack || !free_list) + goto out; + + va_start(ap, fmt); + while(*fmt) { + switch(*fmt) { + case '\n': + line++; + break; + + case ' ': /* Whitespace */ + break; + + case ',': /* Element spacer */ + if(!root) + { + error_set(error, line, + "Unexpected COMMA precedes root element!"); + root = NULL; + goto out; + } + + if(!cur) + { + error_set(error, line, + "Unexpected COMMA outside a list or object!"); + root = NULL; + goto out; + } + + if(key) + { + error_set(error, line, "Expected KEY, got COMMA!"); + root = NULL; + goto out; + } + break; + + case ':': /* Key/value separator */ + if(!key) + { + error_set(error, line, "Got key/value separator without " + "a key preceding it!"); + root = NULL; + goto out; + } + + if(!json_is_object(cur)) + { + error_set(error, line, "Got a key/value separator " + "(':') outside an object!"); + root = NULL; + goto out; + } + + break; + + case ']': /* Close array or object */ + case '}': + + if(key) + { + error_set(error, line, "OBJECT or ARRAY ended with an " + "incomplete key/value pair!"); + root = NULL; + goto out; + } + + if(depth <= 0) + { + error_set(error, line, + "Too many close-brackets '%c'", *fmt); + root = NULL; + goto out; + } + + if(*fmt == ']' && !json_is_array(cur)) + { + error_set(error, line, + "Stray close-array ']' character"); + root = NULL; + goto out; + } + + if(*fmt == '}' && !json_is_object(cur)) + { + error_set(error, line, + "Stray close-object '}' character"); + root = NULL; + goto out; + } + + cur = stack[--depth]; + break; + + case '[': + obj = json_array(); + goto obj_common; + + case '{': + obj = json_object(); + goto obj_common; + + case 's': /* string */ + s = va_arg(ap, char*); + + if(!s) + { + error_set(error, line, + "Refusing to handle a NULL string"); + root = NULL; + goto out; + } + + if(json_is_object(cur) && !key) + { + /* It's a key */ + key = s; + break; + } + + obj = json_string(s); + goto obj_common; + + case 'n': /* null */ + obj = json_null(); + goto obj_common; + + case 'b': /* boolean */ + obj = va_arg(ap, int) ? + json_true() : json_false(); + goto obj_common; + + case 'i': /* integer */ + obj = json_integer(va_arg(ap, int)); + goto obj_common; + + case 'f': /* double-precision float */ + obj = json_real(va_arg(ap, double)); + goto obj_common; + + case 'O': /* a json_t object; increments refcount */ + obj = va_arg(ap, json_t *); + json_incref(obj); + goto obj_common; + + case 'o': /* a json_t object; doesn't increment refcount */ + obj = va_arg(ap, json_t *); + goto obj_common; + +obj_common: free_list[free_count++] = obj; + + /* Root this object to its parent */ + if(json_is_object(cur)) { + if(!key) + { + error_set(error, line, + "Expected key, got identifier '%c'!", *fmt); + root = NULL; + goto out; + } + + json_object_set_new(cur, key, obj); + key = NULL; + } + else if(json_is_array(cur)) + { + json_array_append_new(cur, obj); + } + else if(!root) + { + printf("Rooting\n"); + root = obj; + } + else + { + error_set(error, line, "Can't figure out where to attach " + "'%c' object!", *fmt); + root = NULL; + goto out; + } + + /* If it was a container ('[' or '{'), descend on the stack */ + if(json_is_array(obj) || json_is_object(obj)) + { + stack[depth++] = cur; + cur = obj; + } + + break; + } + fmt++; + } + va_end(ap); + + if(depth != 0) { + error_set(error, line, + "Missing object or array close-brackets in format string"); + root = NULL; + goto out; + } + + /* Success: don't free everything we just built! */ + free_count = 0; + +out: + while(free_count) + json_decref(free_list[--free_count]); + + if(free_list) + free(free_list); + + if(stack) + free(stack); + + return(root); +} + +int json_unpack(json_t *root, json_error_t **error, const char *fmt, ...) { + va_list ap; + + int rv=0; /* Return value */ + int line = 1; /* Line number */ + + /* Keep a stack of containers (lists and objects) */ + int depth = 0; + json_t **stack; + + int array_index = 0; + char *key = NULL; /* Current key in an object */ + + json_t *cur = NULL; /* Current container */ + json_t *obj = NULL; + + int fmt_length = strlen(fmt); + + error_init(error); + + /* Allocation provisioned for worst case */ + stack = calloc(fmt_length, sizeof(json_t *)); + if(!stack) + { + error_set(error, line, "Out of memory!"); + rv = -1; + goto out; + } + + /* Even if we're successful, we need to know if the number of + * arguments provided matches the number of JSON objects. + * We can do this by counting the elements in every array or + * object we open up, and decrementing the count as we visit + * their children. */ + int unvisited = 0; + + va_start(ap, fmt); + while(*fmt) + { + switch(*fmt) + { + case ' ': /* Whitespace */ + break; + + case '\n': /* Line break */ + line++; + break; + + case ',': /* Element spacer */ + + if(!cur) + { + error_set(error, line, + "Unexpected COMMA outside a list or object!"); + rv = -1; + goto out; + } + + if(key) + { + error_set(error, line, "Expected KEY, got COMMA!"); + rv = -1; + goto out; + } + break; + + case ':': /* Key/value separator */ + if(!json_is_object(cur) || !key) + { + error_set(error, line, "Unexpected ':'"); + rv = -1; + goto out; + } + break; + + case '[': + case '{': + /* Fetch object */ + if(!cur) + { + obj = root; + } + else if(json_is_object(cur)) + { + if(!key) + { + error_set(error, line, "Objects can't be keys"); + rv = -1; + goto out; + } + obj = json_object_get(cur, key); + unvisited--; + key = NULL; + } + else if(json_is_array(cur)) + { + obj = json_array_get(cur, array_index); + unvisited--; + array_index++; + } + else + { + assert(0); + } + + /* Make sure we got what we expected */ + if(*fmt=='{' && !json_is_object(obj)) + { + rv = -2; + goto out; + } + + if(*fmt=='[' && !json_is_array(obj)) + { + rv = -2; + goto out; + } + + unvisited += json_is_object(obj) ? + json_object_size(obj) : + json_array_size(obj); + + /* Descend */ + stack[depth++] = cur; + cur = obj; + + key = NULL; + + break; + + + case ']': + case '}': + + if(json_is_array(cur) && *fmt!=']') + { + error_set(error, line, "Missing ']'"); + rv = -1; + goto out; + } + + if(json_is_object(cur) && *fmt!='}') + { + error_set(error, line, "Missing '}'"); + rv = -1; + goto out; + } + + if(key) + { + error_set(error, line, "Unexpected '%c'", *fmt); + rv = -1; + goto out; + } + + if(depth <= 0) + { + error_set(error, line, "Unexpected '%c'", *fmt); + rv = -1; + goto out; + } + + cur = stack[--depth]; + + break; + + case 's': + if(!key && json_is_object(cur)) + { + /* constant string for key */ + key = va_arg(ap, char*); + break; + } + /* fall through */ + + case 'i': /* integer */ + case 'f': /* double-precision float */ + case 'O': /* a json_t object; increments refcount */ + case 'o': /* a json_t object; borrowed reference */ + case 'b': /* boolean */ + case 'n': /* null */ + + /* Fetch object */ + if(!cur) + { + obj = root; + } + else if(json_is_object(cur)) + { + if(!key) + { + error_set(error, line, + "Only strings may be used as keys!"); + rv = -1; + goto out; + } + + obj = json_object_get(cur, key); + unvisited--; + key = NULL; + } + else if(json_is_array(cur)) + { + obj = json_array_get(cur, array_index); + unvisited--; + array_index++; + } + else + { + error_set(error, line, + "Unsure how to retrieve JSON object '%c'", + *fmt); + rv = -1; + goto out; + } + + switch(*fmt) + { + case 's': + if(!json_is_string(obj)) + { + error_set(error, line, + "Type mismatch! Object wasn't a string."); + rv = -2; + goto out; + } + *va_arg(ap, const char**) = json_string_value(obj); + break; + + case 'i': + if(!json_is_integer(obj)) + { + error_set(error, line, + "Type mismatch! Object wasn't an integer."); + rv = -2; + goto out; + } + *va_arg(ap, int*) = json_integer_value(obj); + break; + + case 'b': + if(!json_is_boolean(obj)) + { + error_set(error, line, + "Type mismatch! Object wasn't a boolean."); + rv = -2; + goto out; + } + *va_arg(ap, int*) = json_is_true(obj); + break; + + case 'f': + if(!json_is_number(obj)) + { + error_set(error, line, + "Type mismatch! Object wasn't a real."); + rv = -2; + goto out; + } + *va_arg(ap, double*) = json_number_value(obj); + break; + + case 'O': + json_incref(obj); + /* Fall through */ + + case 'o': + *va_arg(ap, json_t**) = obj; + break; + + case 'n': + /* Don't actually assign anything; we're just happy + * the null turned up as promised in the format + * string. */ + break; + + default: + error_set(error, line, + "Unknown format character '%c'", *fmt); + rv = -1; + goto out; + } + } + fmt++; + } + + /* Return 0 if everything was matched; otherwise the number of JSON + * objects we didn't get to. */ + rv = unvisited; + +out: + va_end(ap); + + if(stack) + free(stack); + + return(rv); +} + +/* vim: ts=4:expandtab:sw=4 + */ diff --git a/test/.gitignore b/test/.gitignore index dca3c1e..cb88169 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -9,3 +9,5 @@ suites/api/test_number suites/api/test_object suites/api/test_simple suites/api/test_cpp +suites/api/test_pack +suites/api/test_unpack diff --git a/test/suites/api/Makefile.am b/test/suites/api/Makefile.am index 7125792..1e4a0bb 100644 --- a/test/suites/api/Makefile.am +++ b/test/suites/api/Makefile.am @@ -8,7 +8,9 @@ check_PROGRAMS = \ test_load \ test_simple \ test_number \ - test_object + test_object \ + test_pack \ + test_unpack test_array_SOURCES = test_array.c util.h test_copy_SOURCES = test_copy.c util.h @@ -17,6 +19,8 @@ test_load_SOURCES = test_load.c util.h test_simple_SOURCES = test_simple.c util.h test_number_SOURCES = test_number.c util.h test_object_SOURCES = test_object.c util.h +test_pack_SOURCES = test_pack.c util.h +test_unpack_SOURCES = test_unpack.c util.h AM_CPPFLAGS = -I$(top_srcdir)/src AM_CFLAGS = -Wall -Werror diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index b1ef400..af22c28 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -55,6 +55,8 @@ json_load_file json_equal json_copy json_deep_copy +json_pack +json_unpack EOF # The list of functions are not exported in the library because they diff --git a/test/suites/api/test_pack.c b/test/suites/api/test_pack.c new file mode 100644 index 0000000..f1ca388 --- /dev/null +++ b/test/suites/api/test_pack.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009, 2010 Petri Lehtinen + * Copyright (c) 2010 Graeme Smecher + * + * 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 +#include "util.h" + +int main() +{ + json_t *value; + int i; + + /* + * Simple, valid json_pack cases + */ + + /* true */ + value = json_pack(NULL, "b", 1); + if(!json_is_true(value)) + fail("json_pack boolean failed"); + if(value->refcount != (ssize_t)-1) + fail("json_pack boolean refcount failed"); + json_decref(value); + + /* false */ + value = json_pack(NULL, "b", 0); + if(!json_is_false(value)) + fail("json_pack boolean failed"); + if(value->refcount != (ssize_t)-1) + fail("json_pack boolean refcount failed"); + json_decref(value); + + /* null */ + value = json_pack(NULL, "n"); + if(!json_is_null(value)) + fail("json_pack null failed"); + if(value->refcount != (ssize_t)-1) + fail("json_pack null refcount failed"); + json_decref(value); + + /* integer */ + value = json_pack(NULL, "i", 1); + if(!json_is_integer(value) || json_integer_value(value) != 1) + fail("json_pack integer failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack integer refcount failed"); + json_decref(value); + + + /* real */ + value = json_pack(NULL, "f", 1.0); + if(!json_is_real(value) || json_real_value(value) != 1.0) + fail("json_pack real failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack real refcount failed"); + json_decref(value); + + /* string */ + value = json_pack(NULL, "s", "test"); + if(!json_is_string(value) || strcmp("test", json_string_value(value))) + fail("json_pack string failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack string refcount failed"); + json_decref(value); + + /* empty object */ + value = json_pack(NULL, "{}", 1.0); + if(!json_is_object(value) || json_object_size(value) != 0) + fail("json_pack empty object failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack empty object refcount failed"); + json_decref(value); + + /* empty list */ + value = json_pack(NULL, "[]", 1.0); + if(!json_is_array(value) || json_array_size(value) != 0) + fail("json_pack empty list failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack empty list failed"); + json_decref(value); + + /* non-incref'd object */ + value = json_pack(NULL, "o", json_integer(1)); + if(!json_is_integer(value) || json_integer_value(value) != 1) + fail("json_pack object failed"); + if(value->refcount != (ssize_t)1) + fail("json_pack integer refcount failed"); + json_decref(value); + + /* incref'd object */ + value = json_pack(NULL, "O", json_integer(1)); + if(!json_is_integer(value) || json_integer_value(value) != 1) + fail("json_pack object failed"); + if(value->refcount != (ssize_t)2) + fail("json_pack integer refcount failed"); + json_decref(value); + json_decref(value); + + /* simple object */ + value = json_pack(NULL, "{s:[]}", "foo"); + if(!json_is_object(value) || json_object_size(value) != 1) + fail("json_pack object failed"); + if(!json_is_array(json_object_get(value, "foo"))) + fail("json_pack object failed"); + if(json_object_get(value, "foo")->refcount != (ssize_t)1) + fail("json_pack object refcount failed"); + json_decref(value); + + /* simple array */ + value = json_pack(NULL, "[i,i,i]", 0, 1, 2); + if(!json_is_array(value) || json_array_size(value) != 3) + fail("json_pack object failed"); + for(i=0; i<3; i++) + { + if(!json_is_integer(json_array_get(value, i)) || + json_integer_value(json_array_get(value, i)) != i) + + fail("json_pack integer array failed"); + } + json_decref(value); + + /* + * Invalid cases + */ + + /* mismatched open/close array/object */ + if(json_pack(NULL, "[}")) + fail("json_pack failed to catch mismatched '}'"); + + if(json_pack(NULL, "{]")) + fail("json_pack failed to catch mismatched ']'"); + + /* missing close array */ + if(json_pack(NULL, "[")) + fail("json_pack failed to catch missing ']'"); + + /* missing close object */ + if(json_pack(NULL, "{")) + fail("json_pack failed to catch missing '}'"); + + /* NULL string */ + if(json_pack(NULL, "s", NULL)) + fail("json_pack failed to catch null string"); + + return(0); +} + +/* vim: ts=4:expandtab:sw=4 + */ diff --git a/test/suites/api/test_unpack.c b/test/suites/api/test_unpack.c new file mode 100644 index 0000000..ea18ff8 --- /dev/null +++ b/test/suites/api/test_unpack.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009, 2010 Petri Lehtinen + * Copyright (c) 2010 Graeme Smecher + * + * 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 +#include "util.h" + +int main() +{ + json_t *j, *j2; + int i1, i2, i3; + int rv; + //void* v; + double f; + char *s; + + /* + * Simple, valid json_pack cases + */ + + /* true */ + rv = json_unpack(json_true(), NULL, "b", &i1); + if(rv || !i1) + fail("json_unpack boolean failed"); + + /* false */ + rv = json_unpack(json_false(), NULL, "b", &i1); + if(rv || i1) + fail("json_unpack boolean failed"); + + /* null */ + rv = json_unpack(json_null(), NULL, "n"); + if(rv) + fail("json_unpack null failed"); + + /* integer */ + j = json_integer(1); + rv = json_unpack(j, NULL, "i", &i1); + if(rv || i1 != 1) + fail("json_unpack integer failed"); + json_decref(j); + + /* real */ + j = json_real(1.0); + rv = json_unpack(j, NULL, "f", &f); + if(rv || f != 1.0) + fail("json_unpack real failed"); + json_decref(j); + + /* string */ + j = json_string("foo"); + rv = json_unpack(j, NULL, "s", &s); + if(rv || strcmp(s, "foo")) + fail("json_unpack string failed"); + json_decref(j); + + /* empty object */ + j = json_object(); + rv = json_unpack(j, NULL, "{}"); + if(rv) + fail("json_unpack empty object failed"); + json_decref(j); + + /* empty list */ + j = json_array(); + rv = json_unpack(j, NULL, "[]"); + if(rv) + fail("json_unpack empty list failed"); + json_decref(j); + + /* non-incref'd object */ + j = json_object(); + rv = json_unpack(j, NULL, "o", &j2); + if(j2 != j || j->refcount != (ssize_t)1) + fail("json_unpack object failed"); + json_decref(j); + + /* incref'd object */ + j = json_object(); + rv = json_unpack(j, NULL, "O", &j2); + if(j2 != j || j->refcount != (ssize_t)2) + fail("json_unpack object failed"); + json_decref(j); + json_decref(j); + + /* simple object */ + j = json_pack(NULL, "{s:i}", "foo", 1); + rv = json_unpack(j, NULL, "{s:i}", "foo", &i1); + if(rv || i1!=1) + fail("json_unpack simple object failed"); + json_decref(j); + + /* simple array */ + j = json_pack(NULL, "[iii]", 1, 2, 3); + rv = json_unpack(j, NULL, "[i,i,i]", &i1, &i2, &i3); + if(rv || i1 != 1 || i2 != 2 || i3 != 3) + fail("json_unpack simple array failed"); + json_decref(j); + + return 0; +} + +/* vim: ts=4:expandtab:sw=4 + */