mirror of
https://github.com/json-c/json-c.git
synced 2026-03-13 18:19:06 +08:00
Add a json_c_visit() function to provide a way to iterate over a tree of json-c objects.
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -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
|
||||
|
||||
133
json_visit.c
Normal file
133
json_visit.c
Normal file
@@ -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
|
||||
}
|
||||
|
||||
91
json_visit.h
Normal file
91
json_visit.h
Normal file
@@ -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=)
|
||||
|
||||
111
tests/test_visit.c
Normal file
111
tests/test_visit.c
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
55
tests/test_visit.expected
Normal file
55
tests/test_visit.expected
Normal file
@@ -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
|
||||
================================
|
||||
|
||||
12
tests/test_visit.test
Executable file
12
tests/test_visit.test
Executable file
@@ -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 $?
|
||||
Reference in New Issue
Block a user