add parser for json path

This commit is contained in:
Jonathan Buchanan 2020-07-21 03:14:41 -04:00
parent 78cc094d47
commit fecfa27727
No known key found for this signature in database
GPG Key ID: 476CBCAEE3E1096C
3 changed files with 319 additions and 0 deletions

View File

@ -183,6 +183,38 @@ TALER_JSON_contract_part_forget (json_t *json,
const char *field);
/**
* Called for each path found after expanding a path.
*
* @param cls the closure.
* @param object_id the name of the object that is pointed to.
* @param parent the parent of the object at @e object_id.
*/
typedef void
(*TALER_JSON_ExpandPathCallback) (
void *cls,
const char *object_id,
json_t *parent);
/**
* Expands a path for a json object. May call the callback several times
* if the path contains a wildcard.
*
* @param json the json object the path references.
* @param path the path to expand. Must begin with "$." and follow dot notation,
* and may include array indices and wildcards.
* @param cb the callback.
* @param cb_cls closure for the callback.
* @return GNUNET_OK on success, GNUNET_SYSERR if @e path is invalid.
*/
int
TALER_JSON_expand_path (json_t *json,
const char *path,
TALER_JSON_ExpandPathCallback cb,
void *cb_cls);
/**
* Extract the Taler error code from the given @a json object.
* Note that #TALER_EC_NONE is returned if no "code" is present.

View File

@ -459,6 +459,149 @@ TALER_JSON_contract_part_forget (json_t *json,
}
/**
* Parse a json path.
*
* @param obj the object that the path is relative to.
* @param prev the parent of @e obj.
* @param path the path to parse.
* @param cb the callback to call, if we get to the end of @e path.
* @param cb_cls the closure for the callback.
* @return GNUNET_OK on success, GNUNET_SYSERR if @e path is malformed.
*/
static int
parse_path (json_t *obj,
json_t *prev,
const char *path,
TALER_JSON_ExpandPathCallback cb,
void *cb_cls)
{
char *id = GNUNET_strdup (path);
char *next_id = strchr (id,
'.');
char *next_path;
char *bracket;
json_t *parent = obj;
json_t *next_obj = NULL;
if (NULL != next_id)
{
bracket = strchr (next_id,
'[');
*next_id = '\0';
next_id++;
next_path = GNUNET_strdup (next_id);
char *next_dot = strchr (next_id,
'.');
if (NULL != next_dot)
*next_dot = '\0';
}
else
{
cb (cb_cls,
id,
prev);
return GNUNET_OK;
}
/* If this is the first time this is called, make sure id is "$" */
if ((NULL == prev) &&
(0 != strcmp (id,
"$")))
return GNUNET_SYSERR;
/* Check for bracketed indices */
if (NULL != bracket)
{
char *end_bracket = strchr (bracket,
']');
if (NULL == end_bracket)
return GNUNET_SYSERR;
*end_bracket = '\0';
*bracket = '\0';
bracket++;
parent = json_object_get (obj,
next_id);
if (0 == strcmp (bracket,
"*"))
{
size_t index;
json_t *value;
int ret = GNUNET_OK;
json_array_foreach (parent, index, value) {
ret = parse_path (value,
obj,
next_path,
cb,
cb_cls);
if (GNUNET_OK != ret)
{
GNUNET_free (id);
return ret;
}
}
}
else
{
unsigned int index;
if (1 != sscanf (bracket,
"%u",
&index))
return GNUNET_SYSERR;
next_obj = json_array_get (parent,
index);
}
}
else
{
/* No brackets, so just fetch the object by name */
next_obj = json_object_get (obj,
next_id);
}
if (NULL != next_obj)
{
return parse_path (next_obj,
parent,
next_path,
cb,
cb_cls);
}
GNUNET_free (id);
GNUNET_free (next_path);
return GNUNET_OK;
}
/**
* Expands a path for a json object. May call the callback several times
* if the path contains a wildcard.
*
* @param json the json object the path references.
* @param path the path to expand. Must begin with "$." and follow dot notation,
* and may include array indices and wildcards.
* @param cb the callback.
* @param cb_cls closure for the callback.
* @return GNUNET_OK on success, GNUNET_SYSERR if @e path is invalid.
*/
int
TALER_JSON_expand_path (json_t *json,
const char *path,
TALER_JSON_ExpandPathCallback cb,
void *cb_cls)
{
return parse_path (json,
NULL,
path,
cb,
cb_cls);
}
/**
* Extract the Taler error code from the given @a json object.
* Note that #TALER_EC_NONE is returned if no "code" is present.

View File

@ -56,6 +56,36 @@ test_amount (void)
}
struct TestPath_Closure
{
const char **object_ids;
const json_t **parents;
unsigned int results_length;
int cmp_result;
};
static void
path_cb (void *cls,
const char *object_id,
json_t *parent)
{
struct TestPath_Closure *cmp = cls;
if (NULL == cmp)
return;
unsigned int i = cmp->results_length;
if ((0 != strcmp (cmp->object_ids[i],
object_id)) ||
(1 != json_equal (cmp->parents[i],
parent)))
cmp->cmp_result = 1;
cmp->results_length += 1;
}
static int
test_contract ()
{
@ -64,6 +94,7 @@ test_contract ()
json_t *c1;
json_t *c2;
json_t *c3;
json_t *c4;
c1 = json_pack ("{s:s, s:{s:s, s:{s:s}}}",
"k1", "v1",
@ -113,6 +144,119 @@ test_contract ()
TALER_JSON_contract_hash (c3,
&h2));
json_decref (c3);
c4 = json_pack ("{s:{s:s}, s:[{s:s}, {s:s}, {s:s}]}",
"abc1",
"xyz", "value",
"fruit",
"name", "banana",
"name", "apple",
"name", "orange");
GNUNET_assert (NULL != c4);
GNUNET_assert (GNUNET_SYSERR ==
TALER_JSON_expand_path (c4,
"%.xyz",
&path_cb,
NULL));
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (c4,
"$.nonexistent_id",
&path_cb,
NULL));
GNUNET_assert (GNUNET_SYSERR ==
TALER_JSON_expand_path (c4,
"$.fruit[n]",
&path_cb,
NULL));
{
const char *object_ids[] = { "xyz" };
const json_t *parents[] = {
json_object_get (c4,
"abc1")
};
struct TestPath_Closure tp = {
.object_ids = object_ids,
.parents = parents,
.results_length = 0,
.cmp_result = 0
};
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (c4,
"$.abc1.xyz",
&path_cb,
&tp));
GNUNET_assert (1 == tp.results_length);
GNUNET_assert (0 == tp.cmp_result);
}
{
const char *object_ids[] = { "name" };
const json_t *parents[] = {
json_array_get (json_object_get (c4,
"fruit"),
0)
};
struct TestPath_Closure tp = {
.object_ids = object_ids,
.parents = parents,
.results_length = 0,
.cmp_result = 0
};
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (c4,
"$.fruit[0].name",
&path_cb,
&tp));
GNUNET_assert (1 == tp.results_length);
GNUNET_assert (0 == tp.cmp_result);
}
{
const char *object_ids[] = { "name", "name", "name" };
const json_t *parents[] = {
json_array_get (json_object_get (c4,
"fruit"),
0),
json_array_get (json_object_get (c4,
"fruit"),
1),
json_array_get (json_object_get (c4,
"fruit"),
2)
};
struct TestPath_Closure tp = {
.object_ids = object_ids,
.parents = parents,
.results_length = 0,
.cmp_result = 0
};
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (c4,
"$.fruit[*].name",
&path_cb,
&tp));
GNUNET_assert (3 == tp.results_length);
GNUNET_assert (0 == tp.cmp_result);
}
{
const char *object_ids[] = { "fruit[0]" };
const json_t *parents[] = {
json_object_get (c4,
"fruit")
};
struct TestPath_Closure tp = {
.object_ids = object_ids,
.parents = parents,
.results_length = 0,
.cmp_result = 0
};
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (c4,
"$.fruit[0]",
&path_cb,
&tp));
GNUNET_assert (1 == tp.results_length);
GNUNET_assert (0 == tp.cmp_result);
}
json_decref (c4);
if (0 !=
GNUNET_memcmp (&h1,
&h2))