Browse Source

Adjusted find_json_token() to work with arrays

Sergey Lyubka 11 years ago
parent
commit
db28d92a8d
3 changed files with 70 additions and 34 deletions
  1. 26 14
      README.md
  2. 25 8
      frozen.c
  3. 19 12
      unit_test.c

+ 26 - 14
README.md

@@ -1,16 +1,17 @@
-JSON parser and generator for ISO C
+JSON parser for ISO C
 ===========================================
 
-## Features
+# Features
 
-   * Written in strict ISO C
-   * No dependencies, even libc is not required
-   * Simple API: one function for parsing, one function for generation
+   * Written in strict ISO C, portable to any environment
+   * Simple API: one function for parsing and one helper function
+     for fetching parsed values
    * Supports superset of JSON: allows non-quoted identifiers as object keys
-   * Very fast, does one pass
+   * Very small footprint
+   * No dependencies
    * Makes no memory allocations
 
-## API
+# API
 
     int parse_json(const char *json_string, int json_string_length,
                    struct json_token *tokens_array, int size_of_tokens_array);
@@ -23,6 +24,7 @@ all `parse_json` will store tokens in the `tokens_array`. Token with type
     struct json_token {
       const char *ptr;    // Points to the beginning of the token
       int len;            // Token length
+      int num_desc;       // For arrays and object, total number of descendants
       int type;           // Type of the token, possible values below
 
     #define JSON_TYPE_EOF     0   // Not a real token. This is a marker
@@ -37,7 +39,10 @@ all `parse_json` will store tokens in the `tokens_array`. Token with type
 
 If `tokens_array` is `NULL`, then `parse_json` just checks the validity of
 the JSON string, and points where parsing stops. If `tokens_array` is not
-`NULL`, it must be pre-allocated by the caller.  
+`NULL`, it must be pre-allocated by the caller. Note that `struct json_token`
+just points to the data inside `json_string`, it does not own the data. Thus
+the token's lifetime is identical to the lifetime of `json_string`, until
+`json_string` is freed or mutated.  
 Return: On success, an offset inside `json_string` is returned
 where parsing has finished. On failure, a negative number is
 returned, one of:
@@ -60,12 +65,14 @@ as if it was written in Javascript. For example, if parsed JSON string is
 that points to number "1".  
 Return: pointer to the found token, or NULL on failure.
 
-## Example: parsing configuration
 
-    static const char *config_str = "{ listening_ports: [ 80, 443 ] } ";
-    struct json_token tokens[100];
+## Example: accessing configuration parameters
+
+    static const char *config_str = " { ports: [ 80, 443 ] } ";
+    struct json_token tokens[10];
     int tokens_size = sizeof(tokens) / sizeof(tokens[0]);
 
+    // Parse config string and make sure tokenization is correct
     ASSERT(parse_json(config_str, strlen(config_str), tokens, tokens_size) > 0);
     ASSERT(tokens[0].type == JSON_TYPE_OBJECT);
     ASSERT(tokens[1].type == JSON_TYPE_STRING);
@@ -73,14 +80,19 @@ Return: pointer to the found token, or NULL on failure.
     ASSERT(tokens[3].type == JSON_TYPE_NUMBER);
     ASSERT(tokens[4].type == JSON_TYPE_NUMBER);
 
+    // Fetch port values
+    ASSERT(find_json_token(tokens, "ports") == &tokens[2]);
+    ASSERT(find_json_token(tokens, "ports[0]") == &tokens[3]);
+    ASSERT(find_json_token(tokens, "ports[1]") == &tokens[4]);
+    ASSERT(find_json_token(tokens, "ports[3]") == NULL);
     ASSERT(find_json_token(tokens, "foo.bar") == NULL);
-    ASSERT(find_json_token(tokens, "listening_ports") == &tokens[2]);
 
-## Licensing
+# Licensing
 
 Frozen is released under [GNU GPL
 v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
-Businesses have an option to get non-restrictive commercial license from
+Businesses have an option to get non-restrictive, royalty-free
+commercial license and professional support from
 [Cesanta Software](http://cesanta.com).
 
 [Super Light DNS Resolver](https://github.com/cesanta/sldr),

+ 25 - 8
frozen.c

@@ -245,7 +245,7 @@ int parse_json(const char *s, int s_len, struct json_token *arr, int arr_len) {
 
 static int path_part_len(const char *p) {
   int i = 0;
-  while (p[i] != '\0' && p[i] != '.') i++;
+  while (p[i] != '\0' && p[i] != '[' && p[i] != '.') i++;
   return i;
 }
 
@@ -253,20 +253,37 @@ const struct json_token *find_json_token(const struct json_token *toks,
                                          const char *path) {
   if (path == 0 && path[0] == '\0') return 0;
   for (;;) {
-    int i, n = path_part_len(path);
-    if (n == 0) return 0;
-    if (toks++->type != JSON_TYPE_OBJECT) return 0;
-    for (i = 0; i < toks[-1].num_desc; i++, toks++) {
-      if (toks[i].type != JSON_TYPE_STRING) return 0;
-      if (toks[i].len == n && compare(path, toks[i].ptr, n)) return &toks[++i];
+    int i, ind2 = 0, ind = -1, skip = 2, n = path_part_len(path);
+    if (path[0] == '\0') return 0;
+    if (path[0] == '[') {
+      if (toks->type != JSON_TYPE_ARRAY || !is_digit(path[1])) return 0;
+      for (ind = 0, n = 1; path[n] != ']' && path[n] != '\0'; n++) {
+        if (!is_digit(path[n])) return 0;
+        ind *= 10;
+        ind += path[n] - '0';
+      }
+      if (path[n++] != ']') return 0;
+      skip = 1;  // In objects, we skip 2 elems while iterating, in arrays 1.
+    } else if (toks->type != JSON_TYPE_OBJECT) return 0;
+    toks++;
+    for (i = 0; i < toks[-1].num_desc; i += skip, ind2++) {
+      // ind == -1 indicated that we're iterating an array, not object
+      if (ind == -1 && toks[i].type != JSON_TYPE_STRING) return 0;
+      if (ind2 == ind ||
+          (ind == -1 && toks[i].len == n && compare(path, toks[i].ptr, n))) {
+        i += skip - 1;
+        break;
+      };
       if (toks[i + 1].type == JSON_TYPE_ARRAY ||
           toks[i + 1].type == JSON_TYPE_OBJECT) {
         i += toks[i + 1].num_desc;
       }
     }
-    toks += i;
+    if (i == toks[-1].num_desc) return 0;
     path += n;
     if (path[0] == '.') path++;
+    if (path[0] == '\0') return &toks[i];
+    toks += i;
   }
   return 0;
 }

+ 19 - 12
unit_test.c

@@ -72,7 +72,7 @@ static const char *test_errors(void) {
     { NULL, 0 }
   };
   const char *s1 = " { a: 1, b: \"hi there\", c: true, d: false, "
-    " e : null, f: [ 1, -2, 3], g: { \"1\": [], h: {} } } ";
+    " e : null, f: [ 1, -2, 3], g: { \"1\": [], h: [ 7 ] } } ";
   int i;
 
   ASSERT(parse_json(NULL, 0, NULL, 0) == JSON_STRING_INVALID);
@@ -100,7 +100,7 @@ static const char *test_errors(void) {
   ASSERT(parse_json(s1, strlen(s1), ar, 10) == JSON_TOKEN_ARRAY_TOO_SMALL);
   ASSERT(parse_json(s1, strlen(s1), ar, size) > 0);
   ASSERT(cmp_token(&ar[0], "{ a: 1, b: \"hi there\", c: true, d: false, "
-                   " e : null, f: [ 1, -2, 3], g: { \"1\": [], h: {} } }",
+                   " e : null, f: [ 1, -2, 3], g: { \"1\": [], h: [ 7 ] } }",
                    JSON_TYPE_OBJECT));
   ASSERT(cmp_token(&ar[1], "a", JSON_TYPE_STRING));
   ASSERT(cmp_token(&ar[2], "1", JSON_TYPE_NUMBER));
@@ -118,18 +118,26 @@ static const char *test_errors(void) {
   ASSERT(cmp_token(&ar[14], "-2", JSON_TYPE_NUMBER));
   ASSERT(cmp_token(&ar[15], "3", JSON_TYPE_NUMBER));
   ASSERT(cmp_token(&ar[16], "g", JSON_TYPE_STRING));
-  ASSERT(cmp_token(&ar[17], "{ \"1\": [], h: {} }" , JSON_TYPE_OBJECT));
+  ASSERT(cmp_token(&ar[17], "{ \"1\": [], h: [ 7 ] }" , JSON_TYPE_OBJECT));
   ASSERT(cmp_token(&ar[18], "1", JSON_TYPE_STRING));
   ASSERT(cmp_token(&ar[19], "[]", JSON_TYPE_ARRAY));
   ASSERT(cmp_token(&ar[20], "h", JSON_TYPE_STRING));
-  ASSERT(cmp_token(&ar[21], "{}", JSON_TYPE_OBJECT));
-  ASSERT(ar[22].type == JSON_TYPE_EOF);
+  ASSERT(cmp_token(&ar[21], "[ 7 ]", JSON_TYPE_ARRAY));
+  ASSERT(cmp_token(&ar[22], "7", JSON_TYPE_NUMBER));
+  ASSERT(ar[23].type == JSON_TYPE_EOF);
+
+  ASSERT(find_json_token(ar, "a") == &ar[2]);
+  ASSERT(find_json_token(ar, "f") == &ar[12]);
+  ASSERT(find_json_token(ar, "g.h") == &ar[21]);
+  ASSERT(find_json_token(ar, "g.h[0]") == &ar[22]);
+  ASSERT(find_json_token(ar, "g.h[1]") == NULL);
+  ASSERT(find_json_token(ar, "g.h1") == NULL);
 
   return NULL;
 }
 
 static const char *test_config(void) {
-  static const char *config_str = "{ listening_ports: [ 80, 443 ] } ";
+  static const char *config_str = "{ ports: [ 80, 443 ] } ";
   struct json_token tokens[100];
   int tokens_size = sizeof(tokens) / sizeof(tokens[0]);
 
@@ -139,14 +147,13 @@ static const char *test_config(void) {
   ASSERT(tokens[2].type == JSON_TYPE_ARRAY);
   ASSERT(tokens[3].type == JSON_TYPE_NUMBER);
   ASSERT(tokens[4].type == JSON_TYPE_NUMBER);
+  ASSERT(tokens[5].type == JSON_TYPE_EOF);
 
+  ASSERT(find_json_token(tokens, "ports") == &tokens[2]);
+  ASSERT(find_json_token(tokens, "ports[0]") == &tokens[3]);
+  ASSERT(find_json_token(tokens, "ports[1]") == &tokens[4]);
+  ASSERT(find_json_token(tokens, "ports[3]") == NULL);
   ASSERT(find_json_token(tokens, "foo.bar") == NULL);
-  ASSERT(find_json_token(tokens, "listening_ports") == &tokens[2]);
-#if 0
-  ASSERT(find_json_token(tokens, "listening_ports[0]") == &tokens[3]);
-  ASSERT(find_json_token(tokens, "listening_ports[1]") == &tokens[4]);
-  ASSERT(find_json_token(tokens, "listening_ports[3]") == NULL);
-#endif
 
   return NULL;
 }