Skip to content

Commit

Permalink
Add a json_c_visit() function to provide a way to iterate over a tree…
Browse files Browse the repository at this point in the history
… of json-c objects.
  • Loading branch information
hawicz committed Oct 29, 2016
1 parent 7e12b9f commit ecdc14f
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 4 deletions.
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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
Expand Down
133 changes: 133 additions & 0 deletions json_visit.c
Original file line number Diff line number Diff line change
@@ -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 changes: 91 additions & 0 deletions json_visit.h
Original file line number Diff line number Diff line change
@@ -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_ */
1 change: 1 addition & 0 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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=)
Expand Down
111 changes: 111 additions & 0 deletions tests/test_visit.c
Original file line number Diff line number Diff line change
@@ -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;
}

Loading

0 comments on commit ecdc14f

Please sign in to comment.