@@ -22,18 +22,19 @@ | |||||
/tests/test4 | /tests/test4 | ||||
/tests/testReplaceExisting | /tests/testReplaceExisting | ||||
/tests/testSubDir | /tests/testSubDir | ||||
/tests/test_parse_int64 | |||||
/tests/test_parse | |||||
/tests/test_cast | /tests/test_cast | ||||
/tests/test_charcase | /tests/test_charcase | ||||
/tests/test_compare | |||||
/tests/test_double_serializer | /tests/test_double_serializer | ||||
/tests/test_locale | /tests/test_locale | ||||
/tests/test_null | /tests/test_null | ||||
/tests/test_parse | |||||
/tests/test_parse_int64 | |||||
/tests/test_printbuf | /tests/test_printbuf | ||||
/tests/test_set_serializer | /tests/test_set_serializer | ||||
/tests/test_compare | |||||
/tests/test_util_file | |||||
/tests/test_set_value | /tests/test_set_value | ||||
/tests/test_util_file | |||||
/tests/test_visit | |||||
/tests/*.vg.out | /tests/*.vg.out | ||||
/tests/*.log | /tests/*.log | ||||
/tests/*.trs | /tests/*.trs | ||||
@@ -28,6 +28,7 @@ libjson_cinclude_HEADERS = \ | |||||
json_object_private.h \ | json_object_private.h \ | ||||
json_tokener.h \ | json_tokener.h \ | ||||
json_util.h \ | json_util.h \ | ||||
json_visit.h \ | |||||
linkhash.h \ | linkhash.h \ | ||||
math_compat.h \ | math_compat.h \ | ||||
printbuf.h \ | printbuf.h \ | ||||
@@ -43,6 +44,7 @@ libjson_c_la_SOURCES = \ | |||||
json_object_iterator.c \ | json_object_iterator.c \ | ||||
json_tokener.c \ | json_tokener.c \ | ||||
json_util.c \ | json_util.c \ | ||||
json_visit.c \ | |||||
linkhash.c \ | linkhash.c \ | ||||
printbuf.c \ | printbuf.c \ | ||||
random_seed.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_set_serializer.test | ||||
TESTS+= test_compare.test | TESTS+= test_compare.test | ||||
TESTS+= test_set_value.test | TESTS+= test_set_value.test | ||||
TESTS+= test_visit.test | |||||
check_PROGRAMS= | check_PROGRAMS= | ||||
check_PROGRAMS += $(TESTS:.test=) | 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 $? |