| @@ -22,18 +22,19 @@ | |||
| /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 | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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_ */ | |||
| @@ -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=) | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| ================================ | |||
| @@ -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 $? | |||