]> granicus.if.org Git - json-c/commitdiff
Add a json_c_visit() function to provide a way to iterate over a tree of json-c objects.
authorEric Haszlakiewicz <erh+git@nimenees.com>
Sat, 29 Oct 2016 19:01:20 +0000 (15:01 -0400)
committerEric Haszlakiewicz <erh+git@nimenees.com>
Sat, 29 Oct 2016 19:01:20 +0000 (15:01 -0400)
.gitignore
Makefile.am
json_visit.c [new file with mode: 0644]
json_visit.h [new file with mode: 0644]
tests/Makefile.am
tests/test_visit.c [new file with mode: 0644]
tests/test_visit.expected [new file with mode: 0644]
tests/test_visit.test [new file with mode: 0755]

index 68b6fcf6026c889d16ac9539667d38c6a6cff1a6..b7098da21b215d2b9bdf8c1160f7e7a9dbe8286a 100644 (file)
 /tests/test4
 /tests/testReplaceExisting
 /tests/testSubDir
-/tests/test_parse_int64
-/tests/test_parse
 /tests/test_cast
 /tests/test_charcase
+/tests/test_compare
 /tests/test_double_serializer
 /tests/test_locale
 /tests/test_null
+/tests/test_parse
+/tests/test_parse_int64
 /tests/test_printbuf
 /tests/test_set_serializer
-/tests/test_compare
-/tests/test_util_file
 /tests/test_set_value
+/tests/test_util_file
+/tests/test_visit
 /tests/*.vg.out
 /tests/*.log
 /tests/*.trs
index c407576045c2bed75fc532b4c0ed7f736052d22b..a5d126b0d26aa487ba1faf9875acaab8db8f9bf4 100644 (file)
@@ -28,6 +28,7 @@ libjson_cinclude_HEADERS = \
        json_object_private.h \
        json_tokener.h \
        json_util.h \
+       json_visit.h \
        linkhash.h \
        math_compat.h \
        printbuf.h \
@@ -43,6 +44,7 @@ libjson_c_la_SOURCES = \
        json_object_iterator.c \
        json_tokener.c \
        json_util.c \
+       json_visit.c \
        linkhash.c \
        printbuf.c \
        random_seed.c
diff --git a/json_visit.c b/json_visit.c
new file mode 100644 (file)
index 0000000..837ffd2
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2016 Eric Haszlakiewicz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See COPYING for details.
+ */
+
+#include <stdio.h>
+
+#include "config.h"
+#include "json_inttypes.h"
+#include "json_object.h"
+#include "json_visit.h"
+#include "linkhash.h"
+
+static int _json_c_visit(json_object *jso, json_object *parent_jso,
+                         const char *jso_key, size_t *jso_index,
+                         json_c_visit_userfunc userfunc, void *userarg);
+
+int json_c_visit(json_object *jso, int future_flags,
+                 json_c_visit_userfunc userfunc, void *userarg)
+{
+       int ret = _json_c_visit(jso, NULL, NULL, NULL, userfunc, userarg);
+       switch(ret)
+       {
+       case JSON_C_VISIT_RETURN_CONTINUE:
+       case JSON_C_VISIT_RETURN_SKIP:
+       case JSON_C_VISIT_RETURN_POP:
+       case JSON_C_VISIT_RETURN_STOP:
+               return 0;
+       default:
+               return JSON_C_VISIT_RETURN_ERROR;
+       }
+}
+static int _json_c_visit(json_object *jso, json_object *parent_jso,
+                         const char *jso_key, size_t *jso_index,
+                         json_c_visit_userfunc userfunc, void *userarg)
+{
+       int userret = userfunc(jso, 0, parent_jso, jso_key, jso_index, userarg);
+       switch(userret)
+       {
+       case JSON_C_VISIT_RETURN_CONTINUE:
+               break;
+       case JSON_C_VISIT_RETURN_SKIP:
+       case JSON_C_VISIT_RETURN_POP:
+       case JSON_C_VISIT_RETURN_STOP:
+       case JSON_C_VISIT_RETURN_ERROR:
+               return userret;
+       default:
+               fprintf(stderr, "ERROR: invalid return value from json_c_visit userfunc: %d\n", userret);
+               return JSON_C_VISIT_RETURN_ERROR;
+       }
+
+       switch(json_object_get_type(jso))
+       {
+       case json_type_null:
+       case json_type_boolean:
+       case json_type_double:
+       case json_type_int:
+       case json_type_string:
+               // we already called userfunc above, move on to the next object
+               return JSON_C_VISIT_RETURN_CONTINUE;
+
+       case json_type_object:
+       {
+               json_object_object_foreach(jso, key, child)
+               {
+                       userret = _json_c_visit(child, jso, key, NULL, userfunc, userarg);
+                       if (userret == JSON_C_VISIT_RETURN_POP)
+                               break;
+                       if (userret == JSON_C_VISIT_RETURN_STOP ||
+                               userret == JSON_C_VISIT_RETURN_ERROR)
+                               return userret;
+                       if (userret != JSON_C_VISIT_RETURN_CONTINUE &&
+                               userret != JSON_C_VISIT_RETURN_SKIP)
+                       {
+                               fprintf(stderr, "INTERNAL ERROR: _json_c_visit returned %d\n", userret);
+                               return JSON_C_VISIT_RETURN_ERROR;
+                       }
+               }
+               break;
+       }
+       case json_type_array:
+       {
+               size_t array_len = json_object_array_length(jso);
+               size_t ii;
+               for (ii = 0; ii < array_len; ii++)
+               {
+                       json_object *child = json_object_array_get_idx(jso, ii);
+                       userret = _json_c_visit(child, jso, NULL, &ii, userfunc, userarg);
+                       if (userret == JSON_C_VISIT_RETURN_POP)
+                               break;
+                       if (userret == JSON_C_VISIT_RETURN_STOP ||
+                               userret == JSON_C_VISIT_RETURN_ERROR)
+                               return userret;
+                       if (userret != JSON_C_VISIT_RETURN_CONTINUE &&
+                               userret != JSON_C_VISIT_RETURN_SKIP)
+                       {
+                               fprintf(stderr, "INTERNAL ERROR: _json_c_visit returned %d\n", userret);
+                               return JSON_C_VISIT_RETURN_ERROR;
+                       }
+               }
+               break;
+       }
+       default:
+               fprintf(stderr, "INTERNAL ERROR: _json_c_visit found object of unknown type: %d\n", json_object_get_type(jso));
+               return JSON_C_VISIT_RETURN_ERROR;
+       }
+
+       // Call userfunc for the second type on container types, after all
+       //  members of the container have been visited.
+       // Non-container types will have already returned before this point.
+
+       userret = userfunc(jso, JSON_C_VISIT_SECOND, parent_jso, jso_key, jso_index, userarg);
+       switch(userret)
+       {
+       case JSON_C_VISIT_RETURN_SKIP:
+       case JSON_C_VISIT_RETURN_POP:
+               // These are not really sensible during JSON_C_VISIT_SECOND, 
+               // but map them to JSON_C_VISIT_CONTINUE anyway.
+               // FALLTHROUGH
+       case JSON_C_VISIT_RETURN_CONTINUE:
+               return JSON_C_VISIT_RETURN_CONTINUE;
+       case JSON_C_VISIT_RETURN_STOP:
+       case JSON_C_VISIT_RETURN_ERROR:
+               return userret;
+       default:
+               fprintf(stderr, "ERROR: invalid return value from json_c_visit userfunc: %d\n", userret);
+               return JSON_C_VISIT_RETURN_ERROR;
+       }
+       // NOTREACHED
+}
+
diff --git a/json_visit.h b/json_visit.h
new file mode 100644 (file)
index 0000000..1d6c68a
--- /dev/null
@@ -0,0 +1,91 @@
+
+#ifndef _json_c_json_visit_h_
+#define _json_c_json_visit_h_
+
+#include "json_object.h"
+
+typedef int (json_c_visit_userfunc)(json_object *jso, int flags,
+                                     json_object *parent_jso,                                                        const char *jso_key,
+                                     size_t *jso_index, void *userarg);
+
+/**
+ * Visit each object in the JSON hierarchy starting at jso.
+ * For each object, userfunc is called, passing the object and userarg.
+ * If the object has a parent (i.e. anything other than jso itself)
+ * its parent will be passed as parent_jso, and either jso_key or jso_index
+ * will be set, depending on whether the parent is an object or an array.
+ *
+ * Nodes will be visited depth first, but containers (arrays and objects)
+ * will be visited twice, the second time with JSON_C_VISIT_SECOND set in
+ * flags.
+ *
+ * userfunc must return one of the defined return values, to indicate
+ * whether and how to continue visiting nodes, or one of various ways to stop.
+ *
+ * Returns 0 if nodes were visited successfully, even if some were 
+ *  intentionally skipped due to what userfunc returned.
+ * Returns <0 if an error occurred during iteration, including if
+ *  userfunc returned JSON_C_VISIT_RETURN_ERROR.
+ */
+int json_c_visit(json_object *jso, int future_flags,
+                 json_c_visit_userfunc userfunc, void *userarg);
+
+/**
+ * Passed to json_c_visit_userfunc as one of the flags values to indicate
+ * that this is the second time a container (array or object) is being
+ * called, after all of it's members have been iterated over.
+ */
+#define JSON_C_VISIT_SECOND  0x02
+
+/**
+ * This json_c_visit_userfunc return value indicates that iteration
+ * should proceed normally.
+ */
+#define JSON_C_VISIT_RETURN_CONTINUE 0
+
+
+/**
+ * This json_c_visit_userfunc return value indicates that iteration
+ * over the members of the current object should be skipped.
+ * If the current object isn't a container (array or object), this
+ * is no different than JSON_C_VISIT_RETURN_CONTINUE.
+ */
+#define JSON_C_VISIT_RETURN_SKIP 7547
+
+/**
+ * This json_c_visit_userfunc return value indicates that iteration
+ * of the fields/elements of the <b>containing</b> object should stop
+ * and continue "popped up" a level of the object hierarchy.
+ * For example, returning this when handling arg will result in 
+ * arg3 and any other fields being skipped.   The next call to userfunc
+ * will be the JSON_C_VISIT_SECOND call on "foo", followed by a userfunc
+ * call on "bar".
+ * <pre>
+ * {
+ *   "foo": {
+ *     "arg1": 1,
+ *     "arg2": 2,
+ *     "arg3": 3,
+ *     ...
+ *   },
+ *   "bar": {
+ *     ...
+ *   }
+ * }
+ * </pre>
+ */
+#define JSON_C_VISIT_RETURN_POP 767
+
+/**
+ * This json_c_visit_userfunc return value indicates that iteration
+ * should stop immediately, and cause json_c_visit to return success.
+ */
+#define JSON_C_VISIT_RETURN_STOP 7867
+
+/**
+ * This json_c_visit_userfunc return value indicates that iteration
+ * should stop immediately, and cause json_c_visit to return an error.
+ */
+#define JSON_C_VISIT_RETURN_ERROR -1
+
+#endif /* _json_c_json_visit_h_ */
index f6e4b2d37cf6b0aad44c3f8122b58d6faee3eb62..1ee06c4c801a2051cd3fd904504b28e0bf5a9bc9 100644 (file)
@@ -22,6 +22,7 @@ TESTS+= test_printbuf.test
 TESTS+= test_set_serializer.test
 TESTS+= test_compare.test
 TESTS+= test_set_value.test
+TESTS+= test_visit.test
 
 check_PROGRAMS=
 check_PROGRAMS += $(TESTS:.test=)
diff --git a/tests/test_visit.c b/tests/test_visit.c
new file mode 100644 (file)
index 0000000..f2ffe8c
--- /dev/null
@@ -0,0 +1,111 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+
+#include "json.h"
+#include "json_tokener.h"
+#include "json_visit.h"
+
+static json_c_visit_userfunc emit_object;
+static json_c_visit_userfunc skip_arrays;
+static json_c_visit_userfunc pop_and_stop;
+static json_c_visit_userfunc err_on_subobj2;
+
+int main(void)
+{
+       MC_SET_DEBUG(1);
+
+       const char *input = "{\
+               \"obj1\": 123,\
+               \"obj2\": {\
+                       \"subobj1\": \"aaa\",\
+                       \"subobj2\": \"bbb\",\
+                       \"subobj3\": [ \"elem1\", \"elem2\", true ],\
+               },\
+               \"obj3\": 1.234,\
+               \"obj4\": [ true, false, null ]\
+       }";
+
+       json_object *jso = json_tokener_parse(input);
+       printf("jso.to_string()=%s\n", json_object_to_json_string(jso));
+
+       int rv;
+       rv = json_c_visit(jso, 0, emit_object, NULL);
+       printf("json_c_visit(emit_object)=%d\n", rv);
+       printf("================================\n\n");
+
+       rv = json_c_visit(jso, 0, skip_arrays, NULL);
+       printf("json_c_visit(skip_arrays)=%d\n", rv);
+       printf("================================\n\n");
+
+       rv = json_c_visit(jso, 0, pop_and_stop, NULL);
+       printf("json_c_visit(pop_and_stop)=%d\n", rv);
+       printf("================================\n\n");
+
+       rv = json_c_visit(jso, 0, err_on_subobj2, NULL);
+       printf("json_c_visit(err_on_subobj2)=%d\n", rv);
+       printf("================================\n\n");
+
+       json_object_put(jso);
+}
+
+
+static int emit_object(json_object *jso, int flags,
+                     json_object *parent_jso,
+                     const char *jso_key,
+                     size_t *jso_index, void *userarg)
+{
+       printf("flags: 0x%x, key: %s, index: %ld, value: %s\n",
+              flags,
+              (jso_key ? jso_key : "(null)"),
+              (jso_index ? (long)*jso_index : -1L),
+              json_object_to_json_string(jso));
+       return JSON_C_VISIT_RETURN_CONTINUE;
+}
+
+static int skip_arrays(json_object *jso, int flags,
+                     json_object *parent_jso,
+                     const char *jso_key,
+                     size_t *jso_index, void *userarg)
+{
+       (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg);
+       if (json_object_get_type(jso) == json_type_array)
+               return JSON_C_VISIT_RETURN_SKIP;
+       return JSON_C_VISIT_RETURN_CONTINUE;
+}
+
+static int pop_and_stop(json_object *jso, int flags,
+                     json_object *parent_jso,
+                     const char *jso_key,
+                     size_t *jso_index, void *userarg)
+{
+       (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg);
+       if (jso_key != NULL && strcmp(jso_key, "subobj1") == 0)
+       {
+               printf("POP after handling subobj1\n");
+               return JSON_C_VISIT_RETURN_POP;
+       }
+       if (jso_key != NULL && strcmp(jso_key, "obj3") == 0)
+       {
+               printf("STOP after handling obj3\n");
+               return JSON_C_VISIT_RETURN_STOP;
+       }
+       return JSON_C_VISIT_RETURN_CONTINUE;
+}
+
+static int err_on_subobj2(json_object *jso, int flags,
+                     json_object *parent_jso,
+                     const char *jso_key,
+                     size_t *jso_index, void *userarg)
+{
+       (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg);
+       if (jso_key != NULL && strcmp(jso_key, "subobj2") == 0)
+       {
+               printf("ERROR after handling subobj1\n");
+               return JSON_C_VISIT_RETURN_ERROR;
+       }
+       return JSON_C_VISIT_RETURN_CONTINUE;
+}
+
diff --git a/tests/test_visit.expected b/tests/test_visit.expected
new file mode 100644 (file)
index 0000000..2f05622
--- /dev/null
@@ -0,0 +1,55 @@
+jso.to_string()={ "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+flags: 0x0, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+flags: 0x0, key: obj1, index: -1, value: 123
+flags: 0x0, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: subobj1, index: -1, value: "aaa"
+flags: 0x0, key: subobj2, index: -1, value: "bbb"
+flags: 0x0, key: subobj3, index: -1, value: [ "elem1", "elem2", true ]
+flags: 0x0, key: (null), index: 0, value: "elem1"
+flags: 0x0, key: (null), index: 1, value: "elem2"
+flags: 0x0, key: (null), index: 2, value: true
+flags: 0x2, key: subobj3, index: -1, value: [ "elem1", "elem2", true ]
+flags: 0x2, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: obj3, index: -1, value: 1.234
+flags: 0x0, key: obj4, index: -1, value: [ true, false, null ]
+flags: 0x0, key: (null), index: 0, value: true
+flags: 0x0, key: (null), index: 1, value: false
+flags: 0x0, key: (null), index: 2, value: null
+flags: 0x2, key: obj4, index: -1, value: [ true, false, null ]
+flags: 0x2, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+json_c_visit(emit_object)=0
+================================
+
+flags: 0x0, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+flags: 0x0, key: obj1, index: -1, value: 123
+flags: 0x0, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: subobj1, index: -1, value: "aaa"
+flags: 0x0, key: subobj2, index: -1, value: "bbb"
+flags: 0x0, key: subobj3, index: -1, value: [ "elem1", "elem2", true ]
+flags: 0x2, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: obj3, index: -1, value: 1.234
+flags: 0x0, key: obj4, index: -1, value: [ true, false, null ]
+flags: 0x2, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+json_c_visit(skip_arrays)=0
+================================
+
+flags: 0x0, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+flags: 0x0, key: obj1, index: -1, value: 123
+flags: 0x0, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: subobj1, index: -1, value: "aaa"
+POP after handling subobj1
+flags: 0x2, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: obj3, index: -1, value: 1.234
+STOP after handling obj3
+json_c_visit(pop_and_stop)=0
+================================
+
+flags: 0x0, key: (null), index: -1, value: { "obj1": 123, "obj2": { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }, "obj3": 1.234, "obj4": [ true, false, null ] }
+flags: 0x0, key: obj1, index: -1, value: 123
+flags: 0x0, key: obj2, index: -1, value: { "subobj1": "aaa", "subobj2": "bbb", "subobj3": [ "elem1", "elem2", true ] }
+flags: 0x0, key: subobj1, index: -1, value: "aaa"
+flags: 0x0, key: subobj2, index: -1, value: "bbb"
+ERROR after handling subobj1
+json_c_visit(err_on_subobj2)=-1
+================================
+
diff --git a/tests/test_visit.test b/tests/test_visit.test
new file mode 100755 (executable)
index 0000000..87e2561
--- /dev/null
@@ -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_visit
+exit $?