wrap-json: add unpacking tool for json
authorJosé Bollo <jose.bollo@iot.bzh>
Thu, 27 Jul 2017 13:55:45 +0000 (15:55 +0200)
committerJosé Bollo <jose.bollo@iot.bzh>
Thu, 27 Jul 2017 14:58:01 +0000 (16:58 +0200)
The functions wrap_json_unpack, wrap_json_vunpack,
wrap_json_check, wrap_json_vcheck, wrap_json_match and
wrap_json_vmatch can be used to get items in a json
object.

These functions are likely to be compatible in the
format with the unpacking functions of the library
jansson [1].

[1] http://jansson.readthedocs.io/en/2.10/apiref.html#parsing-and-validating-values

Change-Id: I18ad702498b9a70215d88b2a3c13bd0f6194bb45
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
src/CMakeLists.txt
src/wrap-json.c
src/wrap-json.h
src/wrap-json.md [new file with mode: 0644]

index 7d2db4a..6db4d4f 100644 (file)
@@ -69,6 +69,7 @@ ADD_LIBRARY(afb-lib STATIC
        sig-monitor.c
        verbose.c
        websock.c
+       wrap-json.c
 )
 
 ###########################################
index 1b4b13e..2354ea7 100644 (file)
 
 #include "wrap-json.h"
 
-#define STACKCOUNT 32
-#define STRCOUNT   8
+#define STACKCOUNT  32
+#define STRCOUNT    8
 
 enum {
-       wrap_json_pack_error_none,
-       wrap_json_pack_error_null_object,
-       wrap_json_pack_error_truncated,
-       wrap_json_pack_error_internal_error,
-       wrap_json_pack_error_out_of_memory,
-       wrap_json_pack_error_invalid_character,
-       wrap_json_pack_error_too_long,
-       wrap_json_pack_error_too_deep,
-       wrap_json_pack_error_null_spec,
-       wrap_json_pack_error_null_key,
-       wrap_json_pack_error_null_string,
-       _wrap_json_pack_error_count_
+       wrap_json_error_none,
+       wrap_json_error_null_object,
+       wrap_json_error_truncated,
+       wrap_json_error_internal_error,
+       wrap_json_error_out_of_memory,
+       wrap_json_error_invalid_character,
+       wrap_json_error_too_long,
+       wrap_json_error_too_deep,
+       wrap_json_error_null_spec,
+       wrap_json_error_null_key,
+       wrap_json_error_null_string,
+       wrap_json_error_out_of_range,
+       wrap_json_error_incomplete,
+       wrap_json_error_missfit_type,
+       wrap_json_error_key_not_found,
+       _wrap_json_error_count_
 };
 
 static const char ignore_all[] = " \t\n\r,:";
-static const char accept_arr[] = "][{snbiIfoO";
-static const char accept_key[] = "s}";
-#define accept_any (&accept_arr[1])
+static const char pack_accept_arr[] = "][{snbiIfoO";
+static const char pack_accept_key[] = "s}";
+#define pack_accept_any (&pack_accept_arr[1])
 
-static const char *pack_errors[_wrap_json_pack_error_count_] =
+static const char unpack_accept_arr[] = "*!][{snbiIfFoO";
+static const char unpack_accept_key[] = "*!s}";
+#define unpack_accept_any (&unpack_accept_arr[3])
+
+static const char *pack_errors[_wrap_json_error_count_] =
 {
-       [wrap_json_pack_error_none] = "unknown error",
-       [wrap_json_pack_error_null_object] = "null object",
-       [wrap_json_pack_error_truncated] = "truncated",
-       [wrap_json_pack_error_internal_error] = "internal error",
-       [wrap_json_pack_error_out_of_memory] = "out of memory",
-       [wrap_json_pack_error_invalid_character] = "invalid character",
-       [wrap_json_pack_error_too_long] = "too long",
-       [wrap_json_pack_error_too_deep] = "too deep",
-       [wrap_json_pack_error_null_spec] = "spec is NULL",
-       [wrap_json_pack_error_null_key] = "key is NULL",
-       [wrap_json_pack_error_null_string] = "string is NULL"
+       [wrap_json_error_none] = "unknown error",
+       [wrap_json_error_null_object] = "null object",
+       [wrap_json_error_truncated] = "truncated",
+       [wrap_json_error_internal_error] = "internal error",
+       [wrap_json_error_out_of_memory] = "out of memory",
+       [wrap_json_error_invalid_character] = "invalid character",
+       [wrap_json_error_too_long] = "too long",
+       [wrap_json_error_too_deep] = "too deep",
+       [wrap_json_error_null_spec] = "spec is NULL",
+       [wrap_json_error_null_key] = "key is NULL",
+       [wrap_json_error_null_string] = "string is NULL",
+       [wrap_json_error_out_of_range] = "array too small",
+       [wrap_json_error_incomplete] = "incomplete container",
+       [wrap_json_error_missfit_type] = "missfit of type",
+       [wrap_json_error_key_not_found] = "key not found"
 };
 
-int wrap_json_pack_error_position(int rc)
+int wrap_json_get_error_position(int rc)
 {
        if (rc < 0)
                rc = -rc;
        return (rc >> 4) + 1;
 }
 
-int wrap_json_pack_error_code(int rc)
+int wrap_json_get_error_code(int rc)
 {
        if (rc < 0)
                rc = -rc;
        return rc & 15;
 }
 
-const char *wrap_json_pack_error_string(int rc)
+const char *wrap_json_get_error_string(int rc)
 {
-       rc = wrap_json_pack_error_code(rc);
+       rc = wrap_json_get_error_code(rc);
        if (rc >= sizeof pack_errors / sizeof *pack_errors)
                rc = 0;
        return pack_errors[rc];
@@ -91,6 +103,7 @@ static inline const char *skip(const char *d)
 
 int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
 {
+       /* TODO: the case of structs with key being single char should be optimized */
        int nstr, notnull, nullable, rc;
        size_t sz, dsz, ssz;
        char *s;
@@ -106,7 +119,7 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
        top = stack;
        top->key = NULL;
        top->cont = NULL;
-       top->acc = accept_any;
+       top->acc = pack_accept_any;
        top->type = 0;
        if (!desc)
                goto null_spec;
@@ -163,7 +176,7 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
                                        dsz -= strs[nstr].sz;
                                        memcpy(&s[dsz], strs[nstr].str, strs[nstr].sz);
                                }
-                               obj = json_object_new_string_len(s, sz);
+                               obj = json_object_new_string_len(s, (int)sz);
                                if (!obj)
                                        goto out_of_memory;
                        } else if (nullable)
@@ -211,11 +224,11 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
                        top->key = NULL;
                        if (c == '[') {
                                top->type = ']';
-                               top->acc = accept_arr;
+                               top->acc = pack_accept_arr;
                                top->cont = json_object_new_array();
                        } else {
                                top->type = '}';
-                               top->acc = accept_key;
+                               top->acc = pack_accept_key;
                                top->cont = json_object_new_object();
                        }
                        if (!top->cont)
@@ -224,7 +237,7 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
                case '}':
                case ']':
                        if (c != top->type || top <= stack)
-                               goto invalid_character;
+                               goto internal_error;
                        obj = (top--)->cont;
                        if (*d == '*' && !(c == '}' ? json_object_object_length(obj) : json_object_array_length(obj))) {
                                json_object_put(obj);
@@ -252,7 +265,7 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
                        if (!obj)
                                goto null_key;
                        top->key = obj;
-                       top->acc = accept_any;
+                       top->acc = pack_accept_any;
                        top->type = ':';
                        break;
                case ':':
@@ -262,41 +275,43 @@ int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
                                d = skip(++d);
                        json_object_put(top->key);
                        top->key = NULL;
-                       top->acc = accept_key;
+                       top->acc = pack_accept_key;
                        top->type = '}';
                        break;
+               default:
+                       goto internal_error;
                }
        }
 
 null_object:
-       rc = wrap_json_pack_error_null_object;
+       rc = wrap_json_error_null_object;
        goto error;
 truncated:
-       rc = wrap_json_pack_error_truncated;
+       rc = wrap_json_error_truncated;
        goto error;
 internal_error:
-       rc = wrap_json_pack_error_internal_error;
+       rc = wrap_json_error_internal_error;
        goto error;
 out_of_memory:
-       rc = wrap_json_pack_error_out_of_memory;
+       rc = wrap_json_error_out_of_memory;
        goto error;
 invalid_character:
-       rc = wrap_json_pack_error_invalid_character;
+       rc = wrap_json_error_invalid_character;
        goto error;
 too_long:
-       rc = wrap_json_pack_error_too_long;
+       rc = wrap_json_error_too_long;
        goto error;
 too_deep:
-       rc = wrap_json_pack_error_too_deep;
+       rc = wrap_json_error_too_deep;
        goto error;
 null_spec:
-       rc = wrap_json_pack_error_null_spec;
+       rc = wrap_json_error_null_spec;
        goto error;
 null_key:
-       rc = wrap_json_pack_error_null_key;
+       rc = wrap_json_error_null_key;
        goto error;
 null_string:
-       rc = wrap_json_pack_error_null_string;
+       rc = wrap_json_error_null_string;
        goto error;
 error:
        do {
@@ -319,10 +334,317 @@ int wrap_json_pack(struct json_object **result, const char *desc, ...)
        return rc;
 }
 
-#if 1
+static int vunpack(struct json_object *object, const char *desc, va_list args, int store)
+{
+       int rc, optionnal, ignore;
+       char c, xacc[2] = { 0, 0 };
+       const char *acc;
+       const char *d, *fit;
+       const char *key;
+       const char **ps;
+       double *pf;
+       int *pi;
+       int64_t *pI;
+       size_t *pz;
+       struct { struct json_object *parent; const char *acc; int index, count; char type; } stack[STACKCOUNT], *top;
+       struct json_object *obj;
+       struct json_object **po;
+
+       xacc[0] = 0;
+       ignore = 0;
+       top = NULL;
+       acc = unpack_accept_any;
+       if (!desc)
+               goto null_spec;
+       d = skip(desc);
+       obj = object;
+       for(;;) {
+               fit = d;
+               c = *d;
+               if (!c)
+                       goto truncated;
+               if (!strchr(acc, c))
+                       goto invalid_character;
+               d = skip(++d);
+               switch(c) {
+               case 's':
+                       if (xacc[0] == '}') {
+                               /* expects a key */
+                               key = va_arg(args, const char *);
+                               if (!key)
+                                       goto null_key;
+                               if (*d != '?')
+                                       optionnal = 0;
+                               else {
+                                       optionnal = 1;
+                                       d = skip(++d);
+                               }
+                               if (ignore)
+                                       ignore++;
+                               else {
+                                       if (json_object_object_get_ex(top->parent, key, &obj)) {
+                                               /* found */
+                                               top->index++;
+                                       } else {
+                                               /* not found */
+                                               if (!optionnal)
+                                                       goto key_not_found;
+                                               ignore = 1;
+                                               obj = NULL;
+                                       }
+                               }
+                               xacc[0] = ':';
+                               acc = unpack_accept_any;
+                               continue;
+                       }
+                       /* get a string */
+                       if (store)
+                               ps = va_arg(args, const char **);
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_string))
+                                       goto missfit;
+                               if (store && ps)
+                                       *ps = json_object_get_string(obj);
+                       }
+                       if (*d == '%') {
+                               d = skip(++d);
+                               if (store) {
+                                       pz = va_arg(args, size_t *);
+                                       if (!ignore && pz)
+                                               *pz = (size_t)json_object_get_string_len(obj);
+                               }
+                       }
+                       break;
+               case 'n':
+                       if (!ignore && !json_object_is_type(obj, json_type_null))
+                               goto missfit;
+                       break;
+               case 'b':
+                       if (store)
+                               pi = va_arg(args, int *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_boolean))
+                                       goto missfit;
+                               if (store && pi)
+                                       *pi = json_object_get_boolean(obj);
+                       }
+                       break;
+               case 'i':
+                       if (store)
+                               pi = va_arg(args, int *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_int))
+                                       goto missfit;
+                               if (store && pi)
+                                       *pi = json_object_get_int(obj);
+                       }
+                       break;
+               case 'I':
+                       if (store)
+                               pI = va_arg(args, int64_t *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_int))
+                                       goto missfit;
+                               if (store && pI)
+                                       *pI = json_object_get_int64(obj);
+                       }
+                       break;
+               case 'f':
+               case 'F':
+                       if (store)
+                               pf = va_arg(args, double *);
+
+                       if (!ignore) {
+                               if (!(json_object_is_type(obj, json_type_double) || (c == 'F' && json_object_is_type(obj, json_type_int))))
+                                       goto missfit;
+                               if (store && pf)
+                                       *pf = json_object_get_double(obj);
+                       }
+                       break;
+               case 'o':
+               case 'O':
+                       if (store) {
+                               po = va_arg(args, struct json_object **);
+                               if (!ignore && po) {
+                                       if (c == 'O')
+                                               obj = json_object_get(obj);
+                                       *po = obj;
+                               }
+                       }
+                       break;
+
+               case '[':
+               case '{':
+                       if (!top)
+                               top = stack;
+                       else if (++top  >= &stack[STACKCOUNT])
+                               goto too_deep;
+
+                       top->acc = acc;
+                       top->type = xacc[0];
+                       top->index = 0;
+                       top->parent = obj;
+                       if (ignore)
+                               ignore++;
+                       if (c == '[') {
+                               if (!ignore) {
+                                       if (!json_object_is_type(obj, json_type_array))
+                                               goto missfit;
+                                       top->count = json_object_array_length(obj);
+                               }
+                               xacc[0] = ']';
+                               acc = unpack_accept_arr;
+                       } else {
+                               if (!ignore) {
+                                       if (!json_object_is_type(obj, json_type_object))
+                                               goto missfit;
+                                       top->count = json_object_object_length(obj);
+                               }
+                               xacc[0] = '}';
+                               acc = unpack_accept_key;
+                               continue;
+                       }
+                       break;
+               case '}':
+               case ']':
+                       if (!top || c != xacc[0])
+                               goto internal_error;
+                       acc = top->acc;
+                       xacc[0] = top->type;
+                       top = top == stack ? NULL : top - 1;
+                       if (ignore)
+                               ignore--;
+                       break;
+               case '!':
+                       if (*d != xacc[0])
+                               goto invalid_character;
+                       if (!ignore && top->index != top->count)
+                               goto incomplete;
+               case '*':
+                       acc = xacc;
+                       continue;
+               default:
+                       goto internal_error;
+               }
+               switch (xacc[0]) {
+               case 0:
+                       if (top)
+                               goto internal_error;
+                       if (*d)
+                               goto invalid_character;
+                       return 0;
+               case ']':
+                       if (!ignore) {
+                               key = strchr(unpack_accept_arr, *d);
+                               if (key && key >= unpack_accept_any) {
+                                       if (top->index >= top->count)
+                                               goto out_of_range;
+                                       obj = json_object_array_get_idx(top->parent, top->index++);
+                               }
+                       }
+                       break;
+               case ':':
+                       acc = unpack_accept_key;
+                       xacc[0] = '}';
+                       if (ignore)
+                               ignore--;
+                       break;
+               default:
+                       goto internal_error;
+               }
+       }
+truncated:
+       rc = wrap_json_error_truncated;
+       goto error;
+internal_error:
+       rc = wrap_json_error_internal_error;
+       goto error;
+invalid_character:
+       rc = wrap_json_error_invalid_character;
+       goto error;
+too_deep:
+       rc = wrap_json_error_too_deep;
+       goto error;
+null_spec:
+       rc = wrap_json_error_null_spec;
+       goto error;
+null_key:
+       rc = wrap_json_error_null_key;
+       goto error;
+out_of_range:
+       rc = wrap_json_error_out_of_range;
+       goto error;
+incomplete:
+       rc = wrap_json_error_incomplete;
+       goto error;
+missfit:
+       rc = wrap_json_error_missfit_type;
+       goto errorfit;
+key_not_found:
+       rc = wrap_json_error_key_not_found;
+       goto error;
+errorfit:
+       d = fit;
+error:
+       rc = rc | (int)((d - desc) << 4);
+       return -rc;
+}
+
+int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args)
+{
+       return vunpack(object, desc, args, 0);
+}
+
+int wrap_json_check(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 0);
+       va_end(args);
+       return rc;
+}
+
+int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args)
+{
+       return !vunpack(object, desc, args, 0);
+}
+
+int wrap_json_match(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 0);
+       va_end(args);
+       return !rc;
+}
+
+int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args)
+{
+       return vunpack(object, desc, args, 1);
+}
+
+int wrap_json_unpack(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 1);
+       va_end(args);
+       return rc;
+}
+
+#if defined(WRAP_JSON_TEST)
 #include <stdio.h>
 
-void T(const char *desc, ...)
+void p(const char *desc, ...)
 {
        int rc;
        va_list args;
@@ -334,69 +656,219 @@ void T(const char *desc, ...)
        if (!rc) 
                printf("  SUCCESS %s\n\n", json_object_to_json_string(result));
        else
-               printf("  ERROR[char %d err %d] %s\n\n", wrap_json_pack_error_position(rc), wrap_json_pack_error_code(rc), wrap_json_pack_error_string(rc));
+               printf("  ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc));
        json_object_put(result);
 }
 
-#define t(...) printf("testing(%s)\n",#__VA_ARGS__); T(__VA_ARGS__);
+const char *xs[10];
+int *xi[10];
+int64_t *xI[10];
+double *xf[10];
+struct json_object *xo[10];
+size_t xz[10];
+
+void u(const char *value, const char *desc, ...)
+{
+       unsigned m, k;
+       int rc;
+       va_list args;
+       struct json_object *obj, *o;
+
+       memset(xs, 0, sizeof xs);
+       memset(xi, 0, sizeof xi);
+       memset(xI, 0, sizeof xI);
+       memset(xf, 0, sizeof xf);
+       memset(xo, 0, sizeof xo);
+       memset(xz, 0, sizeof xz);
+       obj = json_tokener_parse(value);
+       va_start(args, desc);
+       rc = wrap_json_vunpack(obj, desc, args);
+       va_end(args);
+       if (rc) 
+               printf("  ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc));
+       else {
+               value = NULL;
+               printf("  SUCCESS");
+               va_start(args, desc);
+               k = m = 0;
+               while(*desc) {
+                       switch(*desc) {
+                       case '{': m = (m << 1) | 1; k = 1; break;
+                       case '}': m = m >> 1; k = m&1; break;
+                       case '[': m = m << 1; k = 0; break;
+                       case ']': m = m >> 1; k = m&1; break;
+                       case 's': printf(" s:%s", k ? va_arg(args, const char*) : *(va_arg(args, const char**)?:&value)); k ^= m&1; break;
+                       case '%': printf(" %%:%zu", *va_arg(args, size_t*)); k = m&1; break;
+                       case 'n': printf(" n"); k = m&1; break;
+                       case 'b': printf(" b:%d", *va_arg(args, int*)); k = m&1; break;
+                       case 'i': printf(" i:%d", *va_arg(args, int*)); k = m&1; break;
+                       case 'I': printf(" I:%lld", *va_arg(args, int64_t*)); k = m&1; break;
+                       case 'f': printf(" f:%f", *va_arg(args, double*)); k = m&1; break;
+                       case 'F': printf(" F:%f", *va_arg(args, double*)); k = m&1; break;
+                       case 'o': printf(" o:%s", json_object_to_json_string(*va_arg(args, struct json_object**))); k = m&1; break;
+                       case 'O': o = *va_arg(args, struct json_object**); printf(" O:%s", json_object_to_json_string(o)); json_object_put(o); k = m&1; break;
+                       default: break;
+                       }
+                       desc++;
+               }
+               va_end(args);
+               printf("\n\n");
+       }
+       json_object_put(obj);
+}
+
+#define P(...) do{ printf("pack(%s)\n",#__VA_ARGS__); p(__VA_ARGS__); } while(0)
+#define U(...) do{ printf("unpack(%s)\n",#__VA_ARGS__); u(__VA_ARGS__); } while(0)
 
 int main()
 {
        char buffer[4] = {'t', 'e', 's', 't'};
 
-       t("n");
-       t("b", 1);
-       t("b", 0);
-       t("i", 1);
-       t("I", (uint64_t)0x123456789abcdef);
-       t("f", 3.14);
-       t("s", "test");
-       t("s?", "test");
-       t("s?", NULL);
-       t("s#", "test asdf", 4);
-       t("s%", "test asdf", (size_t)4);
-       t("s#", buffer, 4);
-       t("s%", buffer, (size_t)4);
-       t("s++", "te", "st", "ing");
-       t("s#+#+", "test", 1, "test", 2, "test");
-       t("s%+%+", "test", (size_t)1, "test", (size_t)2, "test");
-       t("{}", 1.0);
-       t("[]", 1.0);
-       t("o", json_object_new_int(1));
-       t("o?", json_object_new_int(1));
-       t("o?", NULL);
-       t("O", json_object_new_int(1));
-       t("O?", json_object_new_int(1));
-       t("O?", NULL);
-       t("{s:[]}", "foo");
-       t("{s+#+: []}", "foo", "barbar", 3, "baz");
-       t("{s:s,s:o,s:O}", "a", NULL, "b", NULL, "c", NULL);
-       t("{s:**}", "a", NULL);
-       t("{s:s*,s:o*,s:O*}", "a", NULL, "b", NULL, "c", NULL);
-       t("[i,i,i]", 0, 1, 2);
-       t("[s,o,O]", NULL, NULL, NULL);
-       t("[**]", NULL);
-       t("[s*,o*,O*]", NULL, NULL, NULL);
-       t(" s ", "test");
-       t("[ ]");
-       t("[ i , i,  i ] ", 1, 2, 3);
-       t("{\n\n1");
-       t("[}");
-       t("{]");
-       t("[");
-       t("{");
-       t("[i]a", 42);
-       t("ia", 42);
-       t("s", NULL);
-       t("+", NULL);
-       t(NULL);
-       t("{s:i}", NULL, 1);
-       t("{ {}: s }", "foo");
-       t("{ s: {},  s:[ii{} }", "foo", "bar", 12, 13);
-       t("[[[[[   [[[[[  [[[[ }]]]] ]]]] ]]]]]");
+       P("n");
+       P("b", 1);
+       P("b", 0);
+       P("i", 1);
+       P("I", (uint64_t)0x123456789abcdef);
+       P("f", 3.14);
+       P("s", "test");
+       P("s?", "test");
+       P("s?", NULL);
+       P("s#", "test asdf", 4);
+       P("s%", "test asdf", (size_t)4);
+       P("s#", buffer, 4);
+       P("s%", buffer, (size_t)4);
+       P("s++", "te", "st", "ing");
+       P("s#+#+", "test", 1, "test", 2, "test");
+       P("s%+%+", "test", (size_t)1, "test", (size_t)2, "test");
+       P("{}", 1.0);
+       P("[]", 1.0);
+       P("o", json_object_new_int(1));
+       P("o?", json_object_new_int(1));
+       P("o?", NULL);
+       P("O", json_object_new_int(1));
+       P("O?", json_object_new_int(1));
+       P("O?", NULL);
+       P("{s:[]}", "foo");
+       P("{s+#+: []}", "foo", "barbar", 3, "baz");
+       P("{s:s,s:o,s:O}", "a", NULL, "b", NULL, "c", NULL);
+       P("{s:**}", "a", NULL);
+       P("{s:s*,s:o*,s:O*}", "a", NULL, "b", NULL, "c", NULL);
+       P("[i,i,i]", 0, 1, 2);
+       P("[s,o,O]", NULL, NULL, NULL);
+       P("[**]", NULL);
+       P("[s*,o*,O*]", NULL, NULL, NULL);
+       P(" s ", "test");
+       P("[ ]");
+       P("[ i , i,  i ] ", 1, 2, 3);
+       P("{\n\n1");
+       P("[}");
+       P("{]");
+       P("[");
+       P("{");
+       P("[i]a", 42);
+       P("ia", 42);
+       P("s", NULL);
+       P("+", NULL);
+       P(NULL);
+       P("{s:i}", NULL, 1);
+       P("{ {}: s }", "foo");
+       P("{ s: {},  s:[ii{} }", "foo", "bar", 12, 13);
+       P("[[[[[   [[[[[  [[[[ }]]]] ]]]] ]]]]]");
+
+       U("true", "b", &xi[0]);
+       U("false", "b", &xi[0]);
+       U("null", "n");
+       U("42", "i", &xi[0]);
+       U("123456789", "I", &xI[0]);
+       U("3.14", "f", &xf[0]);
+       U("12345", "F", &xf[0]);
+       U("3.14", "F", &xf[0]);
+       U("\"foo\"", "s", &xs[0]);
+       U("\"foo\"", "s%", &xs[0], &xz[0]);
+       U("{}", "{}");
+       U("[]", "[]");
+       U("{}", "o", &xo[0]);
+       U("{}", "O", &xo[0]);
+       U("{\"foo\":42}", "{si}", "foo", &xi[0]);
+       U("[1,2,3]", "[i,i,i]", &xi[0], &xi[1], &xi[2]);
+       U("{\"a\":1,\"b\":2,\"c\":3}", "{s:i, s:i, s:i}", "a", &xi[0], "b", &xi[1], "c", &xi[2]);
+       U("42", "z");
+       U("null", "[i]");
+       U("[]", "[}");
+       U("{}", "{]");
+       U("[]", "[");
+       U("{}", "{");
+       U("[42]", "[i]a", &xi[0]);
+       U("42", "ia", &xi[0]);
+       U("[]", NULL);
+       U("\"foo\"", "s", NULL);
+       U("42", "s", NULL);
+       U("42", "n");
+       U("42", "b", NULL);
+       U("42", "f", NULL);
+       U("42", "[i]", NULL);
+       U("42", "{si}", "foo", NULL);
+       U("\"foo\"", "n");
+       U("\"foo\"", "b", NULL);
+       U("\"foo\"", "i", NULL);
+       U("\"foo\"", "I", NULL);
+       U("\"foo\"", "f", NULL);
+       U("\"foo\"", "F", NULL);
+       U("true", "s", NULL);
+       U("true", "n");
+       U("true", "i", NULL);
+       U("true", "I", NULL);
+       U("true", "f", NULL);
+       U("true", "F", NULL);
+       U("[42]", "[ii]", &xi[0], &xi[1]);
+       U("{\"foo\":42}", "{si}", NULL, &xi[0]);
+       U("{\"foo\":42}", "{si}", "baz", &xi[0]);
+       U("[1,2,3]", "[iii!]", &xi[0], &xi[1], &xi[2]);
+       U("[1,2,3]", "[ii!]", &xi[0], &xi[1]);
+       U("[1,2,3]", "[ii]", &xi[0], &xi[1]);
+       U("[1,2,3]", "[ii*]", &xi[0], &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi*}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi!}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si*}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si!}", "baz", &xi[0], "foo", &xi[1]);
+       U("[1,{\"foo\":2,\"bar\":null},[3,4]]", "[i{sisn}[ii]]", &xi[0], "foo", &xi[1], "bar", &xi[2], &xi[3]);
+       U("[1,2,3]", "[ii!i]", &xi[0], &xi[1], &xi[2]);
+       U("[1,2,3]", "[ii*i]", &xi[0], &xi[1], &xi[2]);
+       U("{\"foo\":1,\"bar\":2}", "{si!si}", "foo", &xi[1], "bar", &xi[2]);
+       U("{\"foo\":1,\"bar\":2}", "{si*si}", "foo", &xi[1], "bar", &xi[2]);
+       U("{\"foo\":{\"baz\":null,\"bar\":null}}", "{s{sn!}}", "foo", "bar");
+       U("[[1,2,3]]", "[[ii!]]", &xi[0], &xi[1]);
+       U("{}", "{s?i}", "foo", &xi[0]);
+       U("{\"foo\":1}", "{s?i}", "foo", &xi[0]);
+       U("{}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"foo\":[1,2]}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"bar\":{\"baz\":{\"quux\":15}}}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"foo\":{\"bar\":4}}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{\"foo\":{}}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{\"foo\":42,\"baz\":45}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]);
        return 0;
 }
 
 #endif
 
+#if 0
+
 
+    /* Unpack the same item twice */
+    j = json_pack("{s:s, s:i, s:b}", "foo", "bar", "baz", 42, "quux", 1);
+    if(!json_unpack_ex(j, &error, 0, "{s:s,s:s!}", "foo", &s, "foo", &s))
+        fail("json_unpack object with strict validation failed");
+    {
+        const char *possible_errors[] = {
+            "2 object item(s) left unpacked: baz, quux",
+            "2 object item(s) left unpacked: quux, baz"
+        };
+        check_errors(possible_errors, 2, "<validation>", 1, 10, 10);
+    }
+    json_decref(j);
+
+#endif
index 1e95dcf..7fe90ee 100644 (file)
 #include <stdarg.h>
 #include <json-c/json.h>
 
-extern int wrap_json_pack_error_position(int rc);
-extern int wrap_json_pack_error_code(int rc);
-extern const char *wrap_json_pack_error_string(int rc);
+extern int wrap_json_get_error_position(int rc);
+extern int wrap_json_get_error_code(int rc);
+extern const char *wrap_json_get_error_string(int rc);
+
 extern int wrap_json_vpack(struct json_object **result, const char *desc, va_list args);
 extern int wrap_json_pack(struct json_object **result, const char *desc, ...);
 
+extern int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_unpack(struct json_object *object, const char *desc, ...);
+extern int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_check(struct json_object *object, const char *desc, ...);
+extern int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_match(struct json_object *object, const char *desc, ...);
diff --git a/src/wrap-json.md b/src/wrap-json.md
new file mode 100644 (file)
index 0000000..92940f1
--- /dev/null
@@ -0,0 +1,305 @@
+WRAP-JSON facility
+==================
+
+The facility wrap-json is based on the pack/unpack API on the
+libray jansson. The two chapters below are copied from the
+documentation of jansson library copyrighted by Petri Lehtinen
+(see at end).
+
+Building Values
+---------------
+
+This section describes functions that help to create, or *pack*, complex
+JSON values, especially nested objects and arrays. Value building is
+based on a *format string* that is used to tell the functions about the
+expected arguments.
+
+For example, the format string `"i"` specifies a single integer value,
+while the format string `"[ssb]"` or the equivalent `"[s, s, b]"`
+specifies an array value with two strings and a boolean as its items:
+
+    /* Create the JSON integer 42 */
+    wrap_json_pack(&result, "i", 42);
+
+    /* Create the JSON array ["foo", "bar", true] */
+    wrap_json_pack(&result, "[ssb]", "foo", "bar", 1);
+
+Here's the full list of format specifiers. The type in parentheses
+denotes the resulting JSON type, and the type in brackets (if any)
+denotes the C type that is expected as the corresponding argument or
+arguments.
+
+`s` (string) \[const char \*\]
+
+:   Convert a null terminated UTF-8 string to a JSON string.
+
+`s?` (string) \[const char \*\]
+
+:   Like `s`, but if the argument is *NULL*, output a JSON null value.
+
+`s*` (string) \[const char \*\]
+
+:   Like `s`, but if the argument is *NULL*, do not output any value.
+    This format can only be used inside an object or an array. If used
+    inside an object, the corresponding key is additionally suppressed
+    when the value is omitted. See below for an example.
+
+`s#` (string) \[const char \*, int\]
+
+:   Convert a UTF-8 buffer of a given length to a JSON string.
+
+`s%` (string) \[const char \*, size\_t\]
+
+:   Like `s#` but the length argument is of type size\_t.
+
+`+` \[const char \*\]
+
+:   Like `s`, but concatenate to the previous string. Only valid after
+    `s`, `s#`, `+` or `+#`.
+
+`+#` \[const char \*, int\]
+
+:   Like `s#`, but concatenate to the previous string. Only valid after
+    `s`, `s#`, `+` or `+#`.
+
+`+%` (string) \[const char \*, size\_t\]
+
+:   Like `+#` but the length argument is of type size\_t.
+
+`n` (null)
+
+:   Output a JSON null value. No argument is consumed.
+
+`b` (boolean) \[int\]
+
+:   Convert a C int to JSON boolean value. Zero is converted to `false`
+    and non-zero to `true`.
+
+`i` (integer) \[int\]
+
+:   Convert a C int to JSON integer.
+
+`I` (integer) \[json\_int\_t\]
+
+:   Convert a C json\_int\_t to JSON integer.
+
+`f` (real) \[double\]
+
+:   Convert a C double to JSON real.
+
+`o` (any value) \[json\_t \*\]
+
+:   Output any given JSON value as-is. If the value is added to an array
+    or object, the reference to the value passed to `o` is stolen by the
+    container.
+
+`O` (any value) \[json\_t \*\]
+
+:   Like `o`, but the argument's reference count is incremented. This is
+    useful if you pack into an array or object and want to keep the
+    reference for the JSON value consumed by `O` to yourself.
+
+`o?`, `O?` (any value) \[json\_t \*\]
+
+:   Like `o` and `O`, respectively, but if the argument is *NULL*,
+    output a JSON null value.
+
+`o*`, `O*` (any value) \[json\_t \*\]
+
+:   Like `o` and `O`, respectively, but if the argument is *NULL*, do
+    not output any value. This format can only be used inside an object
+    or an array. If used inside an object, the corresponding key is
+    additionally suppressed. See below for an example.
+
+`[fmt]` (array)
+
+:   Build an array with contents from the inner format string. `fmt` may
+    contain objects and arrays, i.e. recursive value building is
+    supported.
+
+`{fmt}` (object)
+
+:   Build an object with contents from the inner format string `fmt`.
+    The first, third, etc. format specifier represent a key, and must be
+    a string (see `s`, `s#`, `+` and `+#` above), as object keys are
+    always strings. The second, fourth, etc. format specifier represent
+    a value. Any value may be an object or array, i.e. recursive value
+    building is supported.
+
+Whitespace, `:` and `,` are ignored.
+
+More examples:
+
+    /* Build an empty JSON object */
+    wrap_json_pack(&result, "{}");
+
+    /* Build the JSON object {"foo": 42, "bar": 7} */
+    wrap_json_pack(&result, "{sisi}", "foo", 42, "bar", 7);
+
+    /* Like above, ':', ',' and whitespace are ignored */
+    wrap_json_pack(&result, "{s:i, s:i}", "foo", 42, "bar", 7);
+
+    /* Build the JSON array [[1, 2], {"cool": true}] */
+    wrap_json_pack(&result, "[[i,i],{s:b}]", 1, 2, "cool", 1);
+
+    /* Build a string from a non-null terminated buffer */
+    char buffer[4] = {'t', 'e', 's', 't'};
+    wrap_json_pack(&result, "s#", buffer, 4);
+
+    /* Concatenate strings together to build the JSON string "foobarbaz" */
+    wrap_json_pack(&result, "s++", "foo", "bar", "baz");
+
+    /* Create an empty object or array when optional members are missing */
+    wrap_json_pack(&result, "{s:s*,s:o*,s:O*}", "foo", NULL, "bar", NULL, "baz", NULL);
+    wrap_json_pack(&result, "[s*,o*,O*]", NULL, NULL, NULL);
+
+Parsing and Validating Values
+-----------------------------
+
+This section describes functions that help to validate complex values
+and extract, or *unpack*, data from them. Like building values
+&lt;apiref-pack&gt;, this is also based on format strings.
+
+While a JSON value is unpacked, the type specified in the format string
+is checked to match that of the JSON value. This is the validation part
+of the process. In addition to this, the unpacking functions can also
+check that all items of arrays and objects are unpacked. This check be
+enabled with the format specifier `!` or by using the flag
+`JSON_STRICT`. See below for details.
+
+Here's the full list of format specifiers. The type in parentheses
+denotes the JSON type, and the type in brackets (if any) denotes the C
+type whose address should be passed.
+
+`s` (string) \[const char \*\]
+
+:   Convert a JSON string to a pointer to a null terminated UTF-8
+    string. The resulting string is extracted by using
+    json\_string\_value() internally, so it exists as long as there are
+    still references to the corresponding JSON string.
+
+`s%` (string) \[const char \*, size\_t \*\]
+
+:   Convert a JSON string to a pointer to a null terminated UTF-8 string
+    and its length.
+
+`n` (null)
+
+:   Expect a JSON null value. Nothing is extracted.
+
+`b` (boolean) \[int\]
+
+:   Convert a JSON boolean value to a C int, so that `true` is converted
+    to 1 and `false` to 0.
+
+`i` (integer) \[int\]
+
+:   Convert a JSON integer to C int.
+
+`I` (integer) \[json\_int\_t\]
+
+:   Convert a JSON integer to C json\_int\_t.
+
+`f` (real) \[double\]
+
+:   Convert a JSON real to C double.
+
+`F` (integer or real) \[double\]
+
+:   Convert a JSON number (integer or real) to C double.
+
+`o` (any value) \[json\_t \*\]
+
+:   Store a JSON value with no conversion to a json\_t pointer.
+
+`O` (any value) \[json\_t \*\]
+
+:   Like `O`, but the JSON value's reference count is incremented.
+
+`[fmt]` (array)
+
+:   Convert each item in the JSON array according to the inner format
+    string. `fmt` may contain objects and arrays, i.e. recursive value
+    extraction is supported.
+
+`{fmt}` (object)
+
+:   Convert each item in the JSON object according to the inner format
+    string `fmt`. The first, third, etc. format specifier represent a
+    key, and must be `s`. The corresponding argument to unpack functions
+    is read as the object key. The second fourth, etc. format specifier
+    represent a value and is written to the address given as the
+    corresponding argument. **Note** that every other argument is read
+    from and every other is written to.
+
+    `fmt` may contain objects and arrays as values, i.e. recursive value
+    extraction is supported.
+
+`!`
+
+:   This special format specifier is used to enable the check that all
+    object and array items are accessed, on a per-value basis. It must
+    appear inside an array or object as the last format specifier before
+    the closing bracket or brace.
+
+`*`
+
+:   This special format specifier is the opposite of `!`. This is the default.
+    It must appear inside an array or object as the last format specifier
+    before the closing bracket or brace.
+
+Whitespace, `:` and `,` are ignored.
+
+Examples:
+
+    /* root is the JSON integer 42 */
+    int myint;
+    wrap_json_unpack(root, "i", &myint);
+    assert(myint == 42);
+
+    /* root is the JSON object {"foo": "bar", "quux": true} */
+    const char *str;
+    int boolean;
+    wrap_json_unpack(root, "{s:s, s:b}", "foo", &str, "quux", &boolean);
+    assert(strcmp(str, "bar") == 0 && boolean == 1);
+
+    /* root is the JSON array [[1, 2], {"baz": null} */
+    wrap_json_check(root, "[[i,i], {s:n}]", "baz");
+    /* returns 0 for validation success, nothing is extracted */
+
+    /* root is the JSON array [1, 2, 3, 4, 5] */
+    int myint1, myint2;
+    wrap_json_unpack(root, "[ii!]", &myint1, &myint2);
+    /* returns -1 for failed validation */
+
+    /* root is an empty JSON object */
+    int myint = 0, myint2 = 0, myint3 = 0;
+    wrap_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 */
+
+
+Copyright
+---------
+
+Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+