From: Petri Lehtinen Date: Thu, 26 Jan 2012 19:13:07 +0000 (+0200) Subject: Add support for optional object keys for json_unpack() and friends X-Git-Tag: v2.4-moonshot~1^2~42 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=jansson.git;a=commitdiff_plain;h=6cb14dd33707cd448f1ea3baee0ab2a6116aee0f Add support for optional object keys for json_unpack() and friends Initial patch by Andrew Thompson. --- diff --git a/doc/apiref.rst b/doc/apiref.rst index f65dc5d..0657edb 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -1035,6 +1035,8 @@ denotes the C type that is expected as the corresponding argument. fourth, etc. format character represent a value. Any value may be an object or array, i.e. recursive value building is supported. +Whitespace, ``:`` and ``,`` are ignored. + The following functions compose the value building API: .. function:: json_t *json_pack(const char *fmt, ...) @@ -1142,6 +1144,11 @@ type whose address should be passed. ``fmt`` may contain objects and arrays as values, i.e. recursive value extraction is supporetd. + .. versionadded:: 2.3 + Any ``s`` representing a key may be suffixed with a ``?`` to + make the key optional. If the key is not found, nothing is + extracted. See below for an example. + ``!`` This special format character is used to enable the check that all object and array items are accessed, on a per-value basis. It @@ -1156,6 +1163,8 @@ type whose address should be passed. or object as the last format character before the closing bracket or brace. +Whitespace, ``:`` and ``,`` are ignored. + The following functions compose the parsing and validation API: .. function:: int json_unpack(json_t *root, const char *fmt, ...) @@ -1222,6 +1231,13 @@ Examples:: json_unpack(root, "[ii!]", &myint1, &myint2); /* returns -1 for failed validation */ + /* root is an empty JSON object */ + int myint = 0, myint2 = 0; + json_unpack(root, "{s?i, s?[ii]}", + "foo", &myint1, + "bar", &myint2, &myint3); + /* myint1, myint2 or myint3 is no touched as "foo" and "bar" don't exist */ + Equality ======== diff --git a/src/pack_unpack.c b/src/pack_unpack.c index 981a216..01838cf 100644 --- a/src/pack_unpack.c +++ b/src/pack_unpack.c @@ -238,7 +238,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap) return -1; } - if(!json_is_object(root)) { + if(root && !json_is_object(root)) { set_error(s, "", "Expected object, got %s", type_name(root)); goto out; @@ -248,6 +248,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap) while(s->token != '}') { const char *key; json_t *value; + int opt = 0; if(strict != 0) { set_error(s, "", "Expected '}' after '%c', got '%c'", @@ -279,10 +280,21 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap) next_token(s); - value = json_object_get(root, key); - if(!value) { - set_error(s, "", "Object item not found: %s", key); - goto out; + if(s->token == '?') { + opt = 1; + next_token(s); + } + + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_object_get(root, key); + if(!value && !opt) { + set_error(s, "", "Object item not found: %s", key); + goto out; + } } if(unpack(s, value, ap)) @@ -295,7 +307,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap) if(strict == 0 && (s->flags & JSON_STRICT)) strict = 1; - if(strict == 1 && key_set.size != json_object_size(root)) { + if(root && strict == 1 && key_set.size != json_object_size(root)) { long diff = (long)json_object_size(root) - (long)key_set.size; set_error(s, "", "%li object item(s) left unpacked", diff); goto out; @@ -313,7 +325,7 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap) size_t i = 0; int strict = 0; - if(!json_is_array(root)) { + if(root && !json_is_array(root)) { set_error(s, "", "Expected array, got %s", type_name(root)); return -1; } @@ -346,11 +358,17 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap) return -1; } - value = json_array_get(root, i); - if(!value) { - set_error(s, "", "Array index %lu out of range", - (unsigned long)i); - return -1; + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_array_get(root, i); + if(!value) { + set_error(s, "", "Array index %lu out of range", + (unsigned long)i); + return -1; + } } if(unpack(s, value, ap)) @@ -363,7 +381,7 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap) if(strict == 0 && (s->flags & JSON_STRICT)) strict = 1; - if(strict == 1 && i != json_array_size(root)) { + if(root && strict == 1 && i != json_array_size(root)) { long diff = (long)json_array_size(root) - (long)i; set_error(s, "", "%li array item(s) left unpacked", diff); return -1; @@ -383,99 +401,118 @@ static int unpack(scanner_t *s, json_t *root, va_list *ap) return unpack_array(s, root, ap); case 's': - if(!json_is_string(root)) { + if(root && !json_is_string(root)) { set_error(s, "", "Expected string, got %s", type_name(root)); return -1; } if(!(s->flags & JSON_VALIDATE_ONLY)) { - const char **str; + const char **target; - str = va_arg(*ap, const char **); - if(!str) { + target = va_arg(*ap, const char **); + if(!target) { set_error(s, "", "NULL string argument"); return -1; } - *str = json_string_value(root); + if(root) + *target = json_string_value(root); } return 0; case 'i': - if(!json_is_integer(root)) { + if(root && !json_is_integer(root)) { set_error(s, "", "Expected integer, got %s", type_name(root)); return -1; } - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, int*) = json_integer_value(root); + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = json_integer_value(root); + } return 0; case 'I': - if(!json_is_integer(root)) { + if(root && !json_is_integer(root)) { set_error(s, "", "Expected integer, got %s", type_name(root)); return -1; } - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, json_int_t*) = json_integer_value(root); + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_int_t *target = va_arg(*ap, json_int_t*); + if(root) + *target = json_integer_value(root); + } return 0; case 'b': - if(!json_is_boolean(root)) { + if(root && !json_is_boolean(root)) { set_error(s, "", "Expected true or false, got %s", type_name(root)); return -1; } - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, int*) = json_is_true(root); + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = json_is_true(root); + } return 0; case 'f': - if(!json_is_real(root)) { + if(root && !json_is_real(root)) { set_error(s, "", "Expected real, got %s", type_name(root)); return -1; } - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, double*) = json_real_value(root); + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_real_value(root); + } return 0; case 'F': - if(!json_is_number(root)) { + if(root && !json_is_number(root)) { set_error(s, "", "Expected real or integer, got %s", type_name(root)); return -1; } - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, double*) = json_number_value(root); + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_number_value(root); + } return 0; case 'O': - if(!(s->flags & JSON_VALIDATE_ONLY)) + if(root && !(s->flags & JSON_VALIDATE_ONLY)) json_incref(root); /* Fall through */ case 'o': - if(!(s->flags & JSON_VALIDATE_ONLY)) - *va_arg(*ap, json_t**) = root; + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_t **target = va_arg(*ap, json_t**); + if(root) + *target = root; + } return 0; case 'n': /* Never assign, just validate */ - if(!json_is_null(root)) { + if(root && !json_is_null(root)) { set_error(s, "", "Expected null, got %s", type_name(root)); return -1; diff --git a/test/suites/api/test_unpack.c b/test/suites/api/test_unpack.c index b259b5f..1268c7a 100644 --- a/test/suites/api/test_unpack.c +++ b/test/suites/api/test_unpack.c @@ -336,4 +336,38 @@ static void run_tests() fail("json_unpack nested array with strict validation failed"); check_error("1 array item(s) left unpacked", "", 1, 5, 5); json_decref(j); + + /* Optional values */ + j = json_object(); + i1 = 0; + if(json_unpack(j, "{s?i}", "foo", &i1)) + fail("json_unpack failed for optional key"); + if(i1 != 0) + fail("json_unpack unpacked an optional key"); + json_decref(j); + + i1 = 0; + j = json_pack("{si}", "foo", 42); + if(json_unpack(j, "{s?i}", "foo", &i1)) + fail("json_unpack failed for an optional value"); + if(i1 != 42) + fail("json_unpack failed to unpack an optional value"); + json_decref(j); + + j = json_object(); + i1 = i2 = i3 = 0; + if(json_unpack(j, "{s?[ii]s?{s{si}}}", + "foo", &i1, &i2, + "bar", "baz", "quux", &i3)) + fail("json_unpack failed for complex optional values"); + if(i1 != 0 || i2 != 0 || i3 != 0) + fail("json_unpack unexpectedly unpacked something"); + json_decref(j); + + j = json_pack("{s{si}}", "foo", "bar", 42); + if(json_unpack(j, "{s?{s?i}}", "foo", "bar", &i1)) + fail("json_unpack failed for complex optional values"); + if(i1 != 42) + fail("json_unpack failed to unpack"); + json_decref(j); }