Add support for optional object keys for json_unpack() and friends
authorPetri Lehtinen <petri@digip.org>
Thu, 26 Jan 2012 19:13:07 +0000 (21:13 +0200)
committerPetri Lehtinen <petri@digip.org>
Thu, 26 Jan 2012 19:16:36 +0000 (21:16 +0200)
Initial patch by Andrew Thompson.

doc/apiref.rst
src/pack_unpack.c
test/suites/api/test_unpack.c

index f65dc5d..0657edb 100644 (file)
@@ -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.
 
     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, ...)
 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.
 
     ``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
 ``!``
     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.
 
     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, ...)
 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 */
 
     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
 ========
 
 Equality
 ========
index 981a216..01838cf 100644 (file)
@@ -238,7 +238,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
         return -1;
     }
 
         return -1;
     }
 
-    if(!json_is_object(root)) {
+    if(root && !json_is_object(root)) {
         set_error(s, "<validation>", "Expected object, got %s",
                   type_name(root));
         goto out;
         set_error(s, "<validation>", "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;
     while(s->token != '}') {
         const char *key;
         json_t *value;
+        int opt = 0;
 
         if(strict != 0) {
             set_error(s, "<format>", "Expected '}' after '%c', got '%c'",
 
         if(strict != 0) {
             set_error(s, "<format>", "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);
 
 
         next_token(s);
 
-        value = json_object_get(root, key);
-        if(!value) {
-            set_error(s, "<validation>", "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, "<validation>", "Object item not found: %s", key);
+                goto out;
+            }
         }
 
         if(unpack(s, value, ap))
         }
 
         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 == 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, "<validation>", "%li object item(s) left unpacked", diff);
         goto out;
         long diff = (long)json_object_size(root) - (long)key_set.size;
         set_error(s, "<validation>", "%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;
 
     size_t i = 0;
     int strict = 0;
 
-    if(!json_is_array(root)) {
+    if(root && !json_is_array(root)) {
         set_error(s, "<validation>", "Expected array, got %s", type_name(root));
         return -1;
     }
         set_error(s, "<validation>", "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;
         }
 
             return -1;
         }
 
-        value = json_array_get(root, i);
-        if(!value) {
-            set_error(s, "<validation>", "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, "<validation>", "Array index %lu out of range",
+                          (unsigned long)i);
+                return -1;
+            }
         }
 
         if(unpack(s, value, ap))
         }
 
         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 == 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, "<validation>", "%li array item(s) left unpacked", diff);
         return -1;
         long diff = (long)json_array_size(root) - (long)i;
         set_error(s, "<validation>", "%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':
             return unpack_array(s, root, ap);
 
         case 's':
-            if(!json_is_string(root)) {
+            if(root && !json_is_string(root)) {
                 set_error(s, "<validation>", "Expected string, got %s",
                           type_name(root));
                 return -1;
             }
 
             if(!(s->flags & JSON_VALIDATE_ONLY)) {
                 set_error(s, "<validation>", "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, "<args>", "NULL string argument");
                     return -1;
                 }
 
                     set_error(s, "<args>", "NULL string argument");
                     return -1;
                 }
 
-                *str = json_string_value(root);
+                if(root)
+                    *target = json_string_value(root);
             }
             return 0;
 
         case 'i':
             }
             return 0;
 
         case 'i':
-            if(!json_is_integer(root)) {
+            if(root && !json_is_integer(root)) {
                 set_error(s, "<validation>", "Expected integer, got %s",
                           type_name(root));
                 return -1;
             }
 
                 set_error(s, "<validation>", "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':
 
             return 0;
 
         case 'I':
-            if(!json_is_integer(root)) {
+            if(root && !json_is_integer(root)) {
                 set_error(s, "<validation>", "Expected integer, got %s",
                           type_name(root));
                 return -1;
             }
 
                 set_error(s, "<validation>", "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':
 
             return 0;
 
         case 'b':
-            if(!json_is_boolean(root)) {
+            if(root && !json_is_boolean(root)) {
                 set_error(s, "<validation>", "Expected true or false, got %s",
                           type_name(root));
                 return -1;
             }
 
                 set_error(s, "<validation>", "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':
 
             return 0;
 
         case 'f':
-            if(!json_is_real(root)) {
+            if(root && !json_is_real(root)) {
                 set_error(s, "<validation>", "Expected real, got %s",
                           type_name(root));
                 return -1;
             }
 
                 set_error(s, "<validation>", "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':
 
             return 0;
 
         case 'F':
-            if(!json_is_number(root)) {
+            if(root && !json_is_number(root)) {
                 set_error(s, "<validation>", "Expected real or integer, got %s",
                           type_name(root));
                 return -1;
             }
 
                 set_error(s, "<validation>", "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':
 
             return 0;
 
         case 'O':
-            if(!(s->flags & JSON_VALIDATE_ONLY))
+            if(root && !(s->flags & JSON_VALIDATE_ONLY))
                 json_incref(root);
             /* Fall through */
 
         case 'o':
                 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 */
 
             return 0;
 
         case 'n':
             /* Never assign, just validate */
-            if(!json_is_null(root)) {
+            if(root && !json_is_null(root)) {
                 set_error(s, "<validation>", "Expected null, got %s",
                           type_name(root));
                 return -1;
                 set_error(s, "<validation>", "Expected null, got %s",
                           type_name(root));
                 return -1;
index b259b5f..1268c7a 100644 (file)
@@ -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", "<validation>", 1, 5, 5);
     json_decref(j);
         fail("json_unpack nested array with strict validation failed");
     check_error("1 array item(s) left unpacked", "<validation>", 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);
 }
 }