json: add utilities function enabling itaration over JSON object
If JSON structure is not known it is hard to use it with current API. To address this gap lets add API that will give more flexibility the user. This patch also adds proper unit tests. Change-Id: I82eb8c8d4a562ee4c9eb5b72c69fe36004dc576e Signed-off-by: Pawel Wodkowski <pawelx.wodkowski@intel.com> Reviewed-on: https://review.gerrithub.io/424009 Tested-by: SPDK CI Jenkins <sys_sgci@intel.com> Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com> Reviewed-by: Jim Harris <james.r.harris@intel.com>
This commit is contained in:
parent
83b1102f7b
commit
439641f772
@ -46,17 +46,18 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
enum spdk_json_val_type {
|
||||
SPDK_JSON_VAL_INVALID,
|
||||
SPDK_JSON_VAL_NULL,
|
||||
SPDK_JSON_VAL_TRUE,
|
||||
SPDK_JSON_VAL_FALSE,
|
||||
SPDK_JSON_VAL_NUMBER,
|
||||
SPDK_JSON_VAL_STRING,
|
||||
SPDK_JSON_VAL_ARRAY_BEGIN,
|
||||
SPDK_JSON_VAL_ARRAY_END,
|
||||
SPDK_JSON_VAL_OBJECT_BEGIN,
|
||||
SPDK_JSON_VAL_OBJECT_END,
|
||||
SPDK_JSON_VAL_NAME,
|
||||
SPDK_JSON_VAL_INVALID = 0,
|
||||
#define SPDK_JSON_VAL_ANY SPDK_JSON_VAL_INVALID
|
||||
SPDK_JSON_VAL_NULL = 1U << 1,
|
||||
SPDK_JSON_VAL_TRUE = 1U << 2,
|
||||
SPDK_JSON_VAL_FALSE = 1U << 3,
|
||||
SPDK_JSON_VAL_NUMBER = 1U << 4,
|
||||
SPDK_JSON_VAL_STRING = 1U << 5,
|
||||
SPDK_JSON_VAL_ARRAY_BEGIN = 1U << 6,
|
||||
SPDK_JSON_VAL_ARRAY_END = 1U << 7,
|
||||
SPDK_JSON_VAL_OBJECT_BEGIN = 1U << 8,
|
||||
SPDK_JSON_VAL_OBJECT_END = 1U << 9,
|
||||
SPDK_JSON_VAL_NAME = 1U << 10,
|
||||
};
|
||||
|
||||
struct spdk_json_val {
|
||||
@ -261,6 +262,74 @@ int spdk_json_write_named_string_fmt_v(struct spdk_json_write_ctx *w, const char
|
||||
int spdk_json_write_named_array_begin(struct spdk_json_write_ctx *w, const char *name);
|
||||
int spdk_json_write_named_object_begin(struct spdk_json_write_ctx *w, const char *name);
|
||||
|
||||
/**
|
||||
* Return JSON value asociated with key \c key_name. Subobjects won't be searched.
|
||||
*
|
||||
* \param object JSON object to be examined
|
||||
* \param key_name name of the key
|
||||
* \param key optional, will be set with found key
|
||||
* \param val optional, will be set with value of the key
|
||||
* \param type search for specific value type. Pass SPDK_JSON_VAL_ANY to match any type.
|
||||
* \return 0 if found or negative error code:
|
||||
* -EINVAL - json object is invalid
|
||||
* -ENOENT - key not found
|
||||
* -EDOM - key exists but value type mismatch.
|
||||
*/
|
||||
int spdk_json_find(struct spdk_json_val *object, const char *key_name, struct spdk_json_val **key,
|
||||
struct spdk_json_val **val, enum spdk_json_val_type type);
|
||||
|
||||
/**
|
||||
* The same as calling \c spdk_json_find() function with \c type set to \c SPDK_JSON_VAL_STRING
|
||||
*
|
||||
* \param object JSON object to be examined
|
||||
* \param key_name name of the key
|
||||
* \param key optional, will be set with found key
|
||||
* \param val optional, will be set with value of the key
|
||||
* \return See \c spdk_json_find
|
||||
*/
|
||||
|
||||
int spdk_json_find_string(struct spdk_json_val *object, const char *key_name,
|
||||
struct spdk_json_val **key, struct spdk_json_val **val);
|
||||
|
||||
/**
|
||||
* The same as calling \c spdk_json_key() function with \c type set to \c SPDK_JSON_VAL_ARRAY_BEGIN
|
||||
*
|
||||
* \param object JSON object to be examined
|
||||
* \param key_name name of the key
|
||||
* \param key optional, will be set with found key
|
||||
* \param value optional, will be set with key value
|
||||
* \return See \c spdk_json_find
|
||||
*/
|
||||
int spdk_json_find_array(struct spdk_json_val *object, const char *key_name,
|
||||
struct spdk_json_val **key, struct spdk_json_val **value);
|
||||
|
||||
/**
|
||||
* Return first JSON value in given JSON object.
|
||||
*
|
||||
* \param object pointer to JSON object begin
|
||||
* \return Pointer to first object or NULL if object is empty or is not an JSON object
|
||||
*/
|
||||
struct spdk_json_val *spdk_json_object_first(struct spdk_json_val *object);
|
||||
|
||||
/**
|
||||
* Return first JSON value in array.
|
||||
*
|
||||
* \param array_begin pointer to JSON array begin
|
||||
* \return Pointer to first JSON value or NULL if array is empty or is not an JSON array.
|
||||
*/
|
||||
|
||||
struct spdk_json_val *spdk_json_array_first(struct spdk_json_val *array_begin);
|
||||
|
||||
/**
|
||||
* Advance to the next JSON value in JSON object or array.
|
||||
*
|
||||
* \warning if \c pos is not JSON key or JSON array element behaviour is undefined.
|
||||
*
|
||||
* \param pos pointer to JSON key if iterating over JSON object or array element
|
||||
* \return next JSON value or NULL if there is no more objects or array elements
|
||||
*/
|
||||
struct spdk_json_val *spdk_json_next(struct spdk_json_val *pos);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -34,6 +34,9 @@
|
||||
#include "spdk/json.h"
|
||||
|
||||
#include "spdk_internal/utf.h"
|
||||
#include "spdk_internal/log.h"
|
||||
|
||||
#define SPDK_JSON_DEBUG(...) SPDK_DEBUGLOG(SPDK_LOG_JSON_UTIL, __VA_ARGS__)
|
||||
|
||||
size_t
|
||||
spdk_json_val_len(const struct spdk_json_val *val)
|
||||
@ -452,3 +455,196 @@ spdk_json_decode_string(const struct spdk_json_val *val, void *out)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static struct spdk_json_val *
|
||||
spdk_json_first(struct spdk_json_val *object, enum spdk_json_val_type type)
|
||||
{
|
||||
/* 'object' must be JSON object or array. 'type' might be combination of these two. */
|
||||
assert((type & (SPDK_JSON_VAL_ARRAY_BEGIN | SPDK_JSON_VAL_OBJECT_BEGIN)) != 0);
|
||||
|
||||
assert(object != NULL);
|
||||
|
||||
if ((object->type & type) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
object++;
|
||||
if (object->len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static struct spdk_json_val *
|
||||
spdk_json_value(struct spdk_json_val *key)
|
||||
{
|
||||
return key->type == SPDK_JSON_VAL_NAME ? key + 1 : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
spdk_json_find(struct spdk_json_val *object, const char *key_name, struct spdk_json_val **key,
|
||||
struct spdk_json_val **val, enum spdk_json_val_type type)
|
||||
{
|
||||
struct spdk_json_val *_key = NULL;
|
||||
struct spdk_json_val *_val = NULL;
|
||||
struct spdk_json_val *it;
|
||||
|
||||
assert(object != NULL);
|
||||
|
||||
for (it = spdk_json_first(object, SPDK_JSON_VAL_ARRAY_BEGIN | SPDK_JSON_VAL_OBJECT_BEGIN);
|
||||
it != NULL;
|
||||
it = spdk_json_next(it)) {
|
||||
if (it->type != SPDK_JSON_VAL_NAME) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spdk_json_strequal(it, key_name) != true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_key) {
|
||||
SPDK_JSON_DEBUG("Duplicate key '%s'", key_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
_key = it;
|
||||
_val = spdk_json_value(_key);
|
||||
|
||||
if (type != SPDK_JSON_VAL_INVALID && (_val->type & type) == 0) {
|
||||
SPDK_JSON_DEBUG("key '%s' type is %#x but expected one of %#x\n", key_name, _val->type, type);
|
||||
return -EDOM;
|
||||
}
|
||||
}
|
||||
|
||||
if (key) {
|
||||
*key = _key;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
*val = _val;
|
||||
}
|
||||
|
||||
return _val ? 0 : -ENOENT;
|
||||
}
|
||||
|
||||
int
|
||||
spdk_json_find_string(struct spdk_json_val *object, const char *key_name,
|
||||
struct spdk_json_val **key, struct spdk_json_val **val)
|
||||
{
|
||||
return spdk_json_find(object, key_name, key, val, SPDK_JSON_VAL_STRING);
|
||||
}
|
||||
|
||||
int
|
||||
spdk_json_find_array(struct spdk_json_val *object, const char *key_name,
|
||||
struct spdk_json_val **key, struct spdk_json_val **val)
|
||||
{
|
||||
return spdk_json_find(object, key_name, key, val, SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
}
|
||||
|
||||
struct spdk_json_val *
|
||||
spdk_json_object_first(struct spdk_json_val *object)
|
||||
{
|
||||
struct spdk_json_val *first = spdk_json_first(object, SPDK_JSON_VAL_OBJECT_BEGIN);
|
||||
|
||||
/* Empty object? */
|
||||
return first && first->type != SPDK_JSON_VAL_OBJECT_END ? first : NULL;
|
||||
}
|
||||
|
||||
struct spdk_json_val *
|
||||
spdk_json_array_first(struct spdk_json_val *array_begin)
|
||||
{
|
||||
struct spdk_json_val *first = spdk_json_first(array_begin, SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
|
||||
/* Empty array? */
|
||||
return first && first->type != SPDK_JSON_VAL_ARRAY_END ? first : NULL;
|
||||
}
|
||||
|
||||
static struct spdk_json_val *
|
||||
spdk_json_skip_object_or_array(struct spdk_json_val *val)
|
||||
{
|
||||
unsigned lvl;
|
||||
enum spdk_json_val_type end_type;
|
||||
struct spdk_json_val *it;
|
||||
|
||||
if (val->type == SPDK_JSON_VAL_OBJECT_BEGIN) {
|
||||
end_type = SPDK_JSON_VAL_OBJECT_END;
|
||||
} else if (val->type == SPDK_JSON_VAL_ARRAY_BEGIN) {
|
||||
end_type = SPDK_JSON_VAL_ARRAY_END;
|
||||
} else {
|
||||
SPDK_JSON_DEBUG("Expected JSON object (%#x) or array (%#x) but got %#x\n",
|
||||
SPDK_JSON_VAL_OBJECT_BEGIN, SPDK_JSON_VAL_ARRAY_END, val->type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lvl = 1;
|
||||
for (it = val + 1; it->type != SPDK_JSON_VAL_INVALID && lvl != 0; it++) {
|
||||
if (it->type == val->type) {
|
||||
lvl++;
|
||||
} else if (it->type == end_type) {
|
||||
lvl--;
|
||||
}
|
||||
}
|
||||
|
||||
/* if lvl != 0 we have invalid JSON object */
|
||||
if (lvl != 0) {
|
||||
SPDK_JSON_DEBUG("Can't find end of object (type: %#x): lvl (%u) != 0)\n", val->type, lvl);
|
||||
it = NULL;
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
struct spdk_json_val *
|
||||
spdk_json_next(struct spdk_json_val *it)
|
||||
{
|
||||
struct spdk_json_val *val, *next;
|
||||
|
||||
switch (it->type) {
|
||||
case SPDK_JSON_VAL_NAME:
|
||||
val = spdk_json_value(it);
|
||||
next = spdk_json_next(val);
|
||||
break;
|
||||
|
||||
/* We are in the middle of an array - get to next entry */
|
||||
case SPDK_JSON_VAL_NULL:
|
||||
case SPDK_JSON_VAL_TRUE:
|
||||
case SPDK_JSON_VAL_FALSE:
|
||||
case SPDK_JSON_VAL_NUMBER:
|
||||
case SPDK_JSON_VAL_STRING:
|
||||
val = it + 1;
|
||||
return val;
|
||||
|
||||
case SPDK_JSON_VAL_ARRAY_BEGIN:
|
||||
case SPDK_JSON_VAL_OBJECT_BEGIN:
|
||||
next = spdk_json_skip_object_or_array(it);
|
||||
break;
|
||||
|
||||
/* Can't go to the next object if started from the end of array or object */
|
||||
case SPDK_JSON_VAL_ARRAY_END:
|
||||
case SPDK_JSON_VAL_OBJECT_END:
|
||||
case SPDK_JSON_VAL_INVALID:
|
||||
return NULL;
|
||||
default:
|
||||
assert(false);
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/* EOF ? */
|
||||
if (next == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (next->type) {
|
||||
case SPDK_JSON_VAL_ARRAY_END:
|
||||
case SPDK_JSON_VAL_OBJECT_END:
|
||||
case SPDK_JSON_VAL_INVALID:
|
||||
return NULL;
|
||||
default:
|
||||
/* Next value */
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
SPDK_LOG_REGISTER_COMPONENT("json_util", SPDK_LOG_JSON_UTIL)
|
||||
|
@ -37,6 +37,9 @@
|
||||
|
||||
#include "json/json_util.c"
|
||||
|
||||
/* For spdk_json_parse() */
|
||||
#include "json/json_parse.c"
|
||||
|
||||
#define NUM_SETUP(x) \
|
||||
snprintf(buf, sizeof(buf), "%s", x); \
|
||||
v.type = SPDK_JSON_VAL_NUMBER; \
|
||||
@ -763,6 +766,158 @@ test_decode_string(void)
|
||||
free(value);
|
||||
}
|
||||
|
||||
char ut_json_text[] =
|
||||
"{"
|
||||
" \"string\": \"Some string data\","
|
||||
" \"object\": { "
|
||||
" \"another_string\": \"Yet anoter string data\","
|
||||
" \"array name with space\": [1, [], {} ]"
|
||||
" },"
|
||||
" \"array\": [ \"Text\", 2, {} ]"
|
||||
"}"
|
||||
;
|
||||
|
||||
static void
|
||||
test_find(void)
|
||||
{
|
||||
struct spdk_json_val *values, *key, *val, *key2, *val2;
|
||||
ssize_t values_cnt;
|
||||
ssize_t rc;
|
||||
|
||||
values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0);
|
||||
SPDK_CU_ASSERT_FATAL(values_cnt > 0);
|
||||
|
||||
values = calloc(values_cnt, sizeof(struct spdk_json_val));
|
||||
SPDK_CU_ASSERT_FATAL(values != NULL);
|
||||
|
||||
rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0);
|
||||
SPDK_CU_ASSERT_FATAL(values_cnt == rc);
|
||||
|
||||
key = val = NULL;
|
||||
rc = spdk_json_find(values, "string", &key, &val, SPDK_JSON_VAL_STRING);
|
||||
CU_ASSERT(rc == 0);
|
||||
|
||||
CU_ASSERT(key != NULL && spdk_json_strequal(key, "string") == true);
|
||||
CU_ASSERT(val != NULL && spdk_json_strequal(val, "Some string data") == true)
|
||||
|
||||
key = val = NULL;
|
||||
rc = spdk_json_find(values, "object", &key, &val, SPDK_JSON_VAL_OBJECT_BEGIN);
|
||||
CU_ASSERT(rc == 0);
|
||||
|
||||
CU_ASSERT(key != NULL && spdk_json_strequal(key, "object") == true);
|
||||
|
||||
/* Find key in "object" by passing SPDK_JSON_VAL_ANY to match any type */
|
||||
key2 = val2 = NULL;
|
||||
rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ANY);
|
||||
CU_ASSERT(rc == 0);
|
||||
CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true);
|
||||
CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
|
||||
/* Find the "array" key in "object" by passing SPDK_JSON_VAL_ARRAY_BEGIN to match only array */
|
||||
key2 = val2 = NULL;
|
||||
rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
CU_ASSERT(rc == 0);
|
||||
CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true);
|
||||
CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
|
||||
/* Negative test - key doesn't exist */
|
||||
key2 = val2 = NULL;
|
||||
rc = spdk_json_find(val, "this_key_does_not_exist", &key2, &val2, SPDK_JSON_VAL_ANY);
|
||||
CU_ASSERT(rc == -ENOENT);
|
||||
|
||||
/* Negative test - key type doesn't match */
|
||||
key2 = val2 = NULL;
|
||||
rc = spdk_json_find(val, "another_string", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
CU_ASSERT(rc == -EDOM);
|
||||
|
||||
free(values);
|
||||
}
|
||||
|
||||
static void
|
||||
test_iterating(void)
|
||||
{
|
||||
struct spdk_json_val *values;
|
||||
struct spdk_json_val *string_key;
|
||||
struct spdk_json_val *object_key, *object_val;
|
||||
struct spdk_json_val *array_key, *array_val;
|
||||
struct spdk_json_val *another_string_key;
|
||||
struct spdk_json_val *array_name_with_space_key, *array_name_with_space_val;
|
||||
struct spdk_json_val *it;
|
||||
ssize_t values_cnt;
|
||||
ssize_t rc;
|
||||
|
||||
values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0);
|
||||
SPDK_CU_ASSERT_FATAL(values_cnt > 0);
|
||||
|
||||
values = calloc(values_cnt, sizeof(struct spdk_json_val));
|
||||
SPDK_CU_ASSERT_FATAL(values != NULL);
|
||||
|
||||
rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0);
|
||||
SPDK_CU_ASSERT_FATAL(values_cnt == rc);
|
||||
|
||||
/* Iterate over object keys. JSON spec doesn't guarantee order of keys in object but
|
||||
* SPDK implementation implicitly does.
|
||||
*/
|
||||
string_key = spdk_json_object_first(values);
|
||||
CU_ASSERT(spdk_json_strequal(string_key, "string") == true);
|
||||
|
||||
object_key = spdk_json_next(string_key);
|
||||
object_val = spdk_json_value(object_key);
|
||||
CU_ASSERT(spdk_json_strequal(object_key, "object") == true);
|
||||
|
||||
array_key = spdk_json_next(object_key);
|
||||
array_val = spdk_json_value(array_key);
|
||||
CU_ASSERT(spdk_json_strequal(array_key, "array") == true);
|
||||
|
||||
/* NULL '}' */
|
||||
CU_ASSERT(spdk_json_next(array_key) == NULL);
|
||||
|
||||
/* Iterate over subobjects */
|
||||
another_string_key = spdk_json_object_first(object_val);
|
||||
CU_ASSERT(spdk_json_strequal(another_string_key, "another_string") == true);
|
||||
|
||||
array_name_with_space_key = spdk_json_next(another_string_key);
|
||||
array_name_with_space_val = spdk_json_value(array_name_with_space_key);
|
||||
CU_ASSERT(spdk_json_strequal(array_name_with_space_key, "array name with space") == true);
|
||||
|
||||
CU_ASSERT(spdk_json_next(array_name_with_space_key) == NULL);
|
||||
|
||||
/* Iterate over array in subobject */
|
||||
it = spdk_json_array_first(array_name_with_space_val);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER);
|
||||
|
||||
it = spdk_json_next(it);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_ARRAY_BEGIN);
|
||||
|
||||
it = spdk_json_next(it);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN);
|
||||
|
||||
it = spdk_json_next(it);
|
||||
CU_ASSERT(it == NULL);
|
||||
|
||||
/* Iterate over array in root object */
|
||||
it = spdk_json_array_first(array_val);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_STRING);
|
||||
|
||||
it = spdk_json_next(it);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER);
|
||||
|
||||
it = spdk_json_next(it);
|
||||
SPDK_CU_ASSERT_FATAL(it != NULL);
|
||||
CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN);
|
||||
|
||||
/* Array end */
|
||||
it = spdk_json_next(it);
|
||||
CU_ASSERT(it == NULL);
|
||||
|
||||
free(values);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CU_pSuite suite = NULL;
|
||||
@ -790,7 +945,9 @@ int main(int argc, char **argv)
|
||||
CU_add_test(suite, "decode_int32", test_decode_int32) == NULL ||
|
||||
CU_add_test(suite, "decode_uint32", test_decode_uint32) == NULL ||
|
||||
CU_add_test(suite, "decode_uint64", test_decode_uint64) == NULL ||
|
||||
CU_add_test(suite, "decode_string", test_decode_string) == NULL) {
|
||||
CU_add_test(suite, "decode_string", test_decode_string) == NULL ||
|
||||
CU_add_test(suite, "find_object", test_find) == NULL ||
|
||||
CU_add_test(suite, "iterating", test_iterating) == NULL) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user