diff --git a/README.md b/README.md index 33c7310..1954b9b 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,50 @@ Date SHA Author ... ``` +###Query +When you simply read JSON and get values without modifying it, you can use '.' and '[]' to traverse object and array hierarchy. +```c +void query_example(void) { + JSON_Value *root_value; + char *json_string; + + /* json string to parse */ + json_string = "{" + "\"list\":[" + "{" + "\"index\":0," + "\"data\":{" + "\"prop\":\"value\"" + "}" + "}," + "{" + "\"index\":1," + "\"data\":{" + "\"prop\":null" + "}" + "}," + "{" + "\"index\":2," + "\"data\":{" + "\"prop\":123" + "}" + "}" + "]" + "}"; + + /* parsing json string */ + root_value = json_parse_string(json_string); + + if (JSONObject == json_type(root_value)) { + printf("Query: \"%s\", Result: \"%s\"\n", ".list[0].data.prop", json_value_query_string(root_value, ".list[0].data.prop")); + printf("Query: \"%s\", Result: \"%d\"\n", ".list[2].data.prop", (int)json_value_query_number(root_value, ".list[2].data.prop")); + } + + /* cleanup code */ + json_value_free(root_value); +} +``` + ###Persistence In this example I'm using parson to save user information to a file and then load it and validate later. ```c diff --git a/parson.c b/parson.c index a2cc438..16936b6 100644 --- a/parson.c +++ b/parson.c @@ -1929,3 +1929,119 @@ void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Fu parson_malloc = malloc_fun; parson_free = free_fun; } + +JSON_Value * json_value_query_value(JSON_Value *value, const char *query) { + const char *query_start = query, *field_start = query, *field_end = NULL; + char *name = NULL; + JSON_Value *return_value = NULL; + int in_bracket = 0, is_digit = 1; + while (query != NULL && *query != '\0') { + switch (*query) { + case '[': + if (in_bracket) { + // Ignore + } else if (field_start == query) { + field_start = query + 1; + in_bracket = 1; + } else if (field_start < query) { + field_end = query; + } + break; + case ']': + if (in_bracket) { + field_end = query; + query++; + } else { + return NULL; + } + break; + case '.': + if (in_bracket) { + // Ignore + } else if (field_start == query) { + if (query_start == query) { + field_start = query + 1; + } else { + // Query ".." + field_end = query; + } + } else if (field_start < query) { + field_end = query; + query++; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + // Keep is_digit = 1 + break; + default : + is_digit = 0; + break; + } + if (field_end != NULL) { + break; + } else { + query++; + } + } + if (query != NULL && query_start < query && !in_bracket && field_end == NULL) { + field_end = query; + } + if (field_end == NULL) { + return NULL; + } + name = parson_strndup(field_start, field_end - field_start); + if (name == NULL) { + return NULL; + } + if (0 < strlen(name)) { + switch (json_type(value)) { + case JSONObject: + if (is_digit) { + return_value = json_object_get_value_at(json_value_get_object(value), atol(name)); + } else { + return_value = json_object_get_value(json_value_get_object(value), name); + } + break; + case JSONArray: + if (!is_digit) { + return NULL; + } + return_value = json_array_get_value(json_value_get_array(value), atol(name)); + break; + default: + return NULL; + } + } else { + return_value = value; + } + parson_free(name); + if (*query != '\0' && return_value != NULL) { + return_value = json_value_query_value(return_value, query); + } + return return_value; +} + +const char * json_value_query_string(JSON_Value *value, const char *query) { + return json_value_get_string(json_value_query_value(value, query)); +} + +JSON_Object * json_value_query_object(JSON_Value *value, const char *query) { + return json_value_get_object(json_value_query_value(value, query)); +} + +JSON_Array * json_value_query_array(JSON_Value *value, const char *query) { + return json_value_get_array(json_value_query_value(value, query)); +} + +double json_value_query_number(JSON_Value *value, const char *query) { + return json_value_get_number(json_value_query_value(value, query)); +} + +int json_value_query_boolean(JSON_Value *value, const char *query) { + return json_value_get_boolean(json_value_query_value(value, query)); +} + +JSON_Value * json_query(JSON_Value *value, const char *query) { + return json_value_query_value(value, query); +} diff --git a/parson.h b/parson.h index 6a89982..e2ec208 100644 --- a/parson.h +++ b/parson.h @@ -224,6 +224,15 @@ const char * json_string (const JSON_Value *value); double json_number (const JSON_Value *value); int json_boolean(const JSON_Value *value); +/* Query */ +JSON_Value * json_value_query_value (JSON_Value *value, const char *query); +const char * json_value_query_string (JSON_Value *value, const char *query); +JSON_Object * json_value_query_object (JSON_Value *value, const char *query); +JSON_Array * json_value_query_array (JSON_Value *value, const char *query); +double json_value_query_number (JSON_Value *value, const char *query); /* returns 0 on fail */ +int json_value_query_boolean(JSON_Value *value, const char *query); /* returns -1 on fail */ +JSON_Value * json_query (JSON_Value *value, const char *query); /* shorter version of json_value_query_value */ + #ifdef __cplusplus } #endif diff --git a/tests.c b/tests.c index 819711d..a25a8ac 100644 --- a/tests.c +++ b/tests.c @@ -208,6 +208,36 @@ void test_suite_2(JSON_Value *root_value) { TEST(json_object_get_object(root_object, "empty object") != NULL); TEST(json_object_get_array(root_object, "empty array") != NULL); + TEST((json_value_query_value(root_value, ".") == root_value)); + TEST((json_value_query_value(root_value, "..") == root_value)); + TEST((json_value_query_value(root_value, "...") == root_value)); + TEST((json_value_query_value(root_value, "[]") == root_value)); + TEST((json_value_query_value(root_value, NULL) == NULL)); + TEST((json_value_query_value(root_value, "") == NULL)); + TEST((json_value_query_value(root_value, "noexist") == NULL)); + TEST((json_value_query_value(root_value, ".noexist") == NULL)); + TEST((json_value_query_value(root_value, "..noexist") == NULL)); + TEST((json_value_query_value(root_value, "noexist.prop") == NULL)); + TEST((json_value_query_value(root_value, ".noexist.prop") == NULL)); + TEST((json_value_query_value(root_value, "..noexist.prop") == NULL)); + TEST((json_value_query_value(root_value, ".[.no[[exist].prop") == NULL)); + TEST((json_value_query_value(root_value, ".object].nested true") == NULL)); + TEST(STREQ(json_value_query_string(root_value, "[0]"), "lorem ipsum")); + TEST(STREQ(json_value_query_string(root_value, "object.nested object.lorem"), "ipsum")); + TEST(STREQ(json_value_query_string(root_value, "object.nested object[0]"), "ipsum")); + TEST(STREQ(json_value_query_string(root_value, "object.nested array[0]"), "lorem")); + TEST(STREQ(json_value_query_string(root_value, ".object[nested array][1]"), "ipsum")); + TEST((json_value_query_number(root_value, ".object[nested number]") == 123)); + TEST((json_value_query_boolean(root_value, ".object[nested true]") == 1)); + + array = json_value_query_array(root_value, ".object[nested array][]"); + TEST(array != NULL); + TEST(json_array_get_count(array) == 2); + + array = json_value_query_array(root_value, ".[x^2 array][]"); + TEST(array != NULL); + TEST(json_array_get_count(array) == 11); + } void test_suite_2_no_comments(void) {