diff --git a/include/spdk/json.h b/include/spdk/json.h index 62db4439c..8109e5188 100644 --- a/include/spdk/json.h +++ b/include/spdk/json.h @@ -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 diff --git a/lib/json/json_util.c b/lib/json/json_util.c index f9669d468..1146e6fad 100644 --- a/lib/json/json_util.c +++ b/lib/json/json_util.c @@ -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) diff --git a/test/unit/lib/json/json_util.c/json_util_ut.c b/test/unit/lib/json/json_util.c/json_util_ut.c index 2afd963e2..203b744e5 100644 --- a/test/unit/lib/json/json_util.c/json_util_ut.c +++ b/test/unit/lib/json/json_util.c/json_util_ut.c @@ -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(); }