diff options
| author | Joey Adams | 2010-07-22 16:00:15 +0000 |
|---|---|---|
| committer | Joey Adams | 2010-07-22 16:00:15 +0000 |
| commit | 9dd13c566fe14be9d3c807e9d4e140c5a9c6ad9d (patch) | |
| tree | 45dff16951a1d7f1724a733d4dacfdbf50dd496f /json.c | |
| parent | abf3491883d45ce492c1d1a06c3fd01c2e8f90d8 (diff) | |
* Added json_set(json, json_path text, json) function.
* Reworked json_node::orig so it can handle replacing the key or value
without altering surrounding formatting.
* json_encode (C function) is now recursive (its stack usage depends
on the depth of input). This was done because json_encode cannot rely
on nodes' ->parent fields being trustworthy, as json_replace_value
assigns JSON nodes by reference.
Diffstat (limited to 'json.c')
| -rw-r--r-- | json.c | 339 |
1 files changed, 193 insertions, 146 deletions
@@ -129,7 +129,9 @@ typedef struct { } String[1]; /* Declare and initialize a String with the given name. */ -#define String(name) String name = {{NULL, 0, 0}} +#define String(name) String name = NewString() + +#define NewString() {{NULL, 0, 0}} /* Grow the string by @need characters, reallocating if necessary. * Returns a pointer to the uninitialized range where text is to go. @@ -216,7 +218,21 @@ json_node *json_mknumber(const char *number, size_t length) return node; } -void json_append(json_node *parent, json_node *child) +/* Indicate that the node's value has changed, + * marking ancestors as necessary. + * + * Call json_touch_value so that json_encode(, JSONOPT_ORIG) + * will encode the new value rather than using original text. + */ +void json_touch_value(json_node *node) +{ + while (node && node->orig.value.start) { + node->orig.value.start = NULL; + node = node->parent; + } +} + +static void json_append_notouch(json_node *parent, json_node *child) { Assert(parent->type==JSON_ARRAY || parent->type==JSON_OBJECT); Assert(child->parent == NULL); @@ -234,6 +250,12 @@ void json_append(json_node *parent, json_node *child) } } +void json_append(json_node *parent, json_node *child) +{ + json_append_notouch(parent, child); + json_touch_value(parent); +} + void json_remove(json_node *node) { json_node *parent = node->parent; @@ -256,6 +278,18 @@ void json_remove(json_node *node) node->parent = NULL; node->prev = NULL; node->next = NULL; + + json_touch_value(parent); +} + +void json_replace_value(json_node *node, json_node *replacement) +{ + node->type = replacement->type; + node->v = replacement->v; + node->orig.value = replacement->orig.value; + + if (node->parent) + json_touch_value(node->parent); } const char *json_get_string(json_node *node, size_t *length_out) @@ -278,6 +312,7 @@ void json_set_string(json_node *node, const char *str, size_t length) node->v.string.str = NULL; node->v.string.length = 0; } + json_touch_value(node); } const char *json_get_number(json_node *node) @@ -295,6 +330,7 @@ void json_set_number(json_node *node, const char *number, size_t length) node->v.number = JSON_strdup(number, length); else node->v.number = NULL; + json_touch_value(node); } /* Non-recursively free a node */ @@ -366,53 +402,71 @@ bool json_validate(const char *str) json_node *json_decode(const char *str) { - json_node *root = NULL, *parent = NULL, *node = NULL; - const char *s = str; - char *key = NULL; - size_t key_length = 0; - const char *start = NULL; - const char *key_start = NULL; - const char *key_end = NULL; - const char *value_start = NULL; - + json_node *root = NULL, *parent = NULL, *node = NULL; + const char *s = str; + char *key; + size_t key_length; + struct json_node_orig orig; + bool expect_endp; + if (!str) return NULL; if (!utf8_validate(str, strlen(str))) return NULL; - + + expect_endp = false; goto item; -item: /* Expect a value */ - start = s; +item: /* Expect a value (set expect_endp before goto item; ) */ + key = NULL; + key_length = 0; + memset(&orig, 0, sizeof(orig)); + + orig.key_left_space.start = s; + orig.left_space.start = s; + skip_whitespace(&s); + + if (expect_endp) { + if (*s == ']' || *s == '}') + goto endp; + } if (parent && parent->type == JSON_OBJECT) { /* Parse member key string. */ - key_start = s; + orig.key_left_space.end = s; + orig.key.start = s; + key = json_decode_string(&s, &key_length, true); - key_end = s; if (!key) goto failed; + + orig.key.end = s; + orig.key_right_space.start = s; /* Eat the " : " */ skip_whitespace(&s); if (*s != ':') goto failed; + + orig.key_right_space.end = s; s++; + orig.left_space.start = s; + skip_whitespace(&s); - } else { - key = NULL; } - /* The way node->orig.value_start and node->orig.value_end are initialized - is a little funky. value_start is s (the current position in the JSON text), - so that one's easy. value_end is easy unless we're parsing an object or array. - For an object/array, we have to wait until we're done parsing it - (see state 'endp' ) before we can set value_end. + /* + * The way orig.value and company are initialized is a bit funky. + * If this node has children, we have to finish parsing the node's + * children before we know where it ends. Hence, initialization + * of orig.value_end and after will be deferred if this node has children. */ + + orig.left_space.end = s; + orig.value.start = s; - value_start = s; node = decode_leaf(&s); if (!node) { if (*s == '[') @@ -423,39 +477,38 @@ item: /* Expect a value */ goto failed; s++; - /* node->orig.value_end is dangling (actually NULL) for now, + /* orig.value.end and later are dangling (actually NULL) for now, but will be initialized when we get to state 'endp' . */ } else { - node->orig.value_end = s; + orig.value.end = s; + orig.right_space.start = s; + skip_whitespace(&s); - node->orig.end = s; + + orig.right_space.end = s; } - node->orig.used = 1; - node->orig.start = start; - node->orig.value_start = value_start; - if (key) { - node->key = key; - node->key_length = key_length; + node->key = key; + node->key_length = key_length; - /* The key now belongs to the node. This prevents a double free - on failure (see the failed: label). */ - key = NULL; + /* The key now belongs to the node. This prevents a double free + on failure (see the failed: label). */ + key = NULL; - node->orig.key_start = key_start; - node->orig.key_end = key_end; - } + node->orig = orig; if (parent) - json_append(parent, node); + json_append_notouch(parent, node); else root = node; if (is_internal(node)) { /* "push" node onto the "stack". Nodes point up to their parents, - which is why json_encode, json_decode, etc. don't need a "stack" per se. */ + which is why this function doesn't need a "stack" per se. */ parent = node; - goto item_endp; + + expect_endp = true; + goto item; } if (parent) @@ -466,6 +519,8 @@ item: /* Expect a value */ comma_endp: /* Expect a comma or end bracket/brace */ if (*s == ',') { s++; + + expect_endp = false; goto item; } if (*s == ']' || *s == '}') @@ -473,12 +528,6 @@ comma_endp: /* Expect a comma or end bracket/brace */ goto failed; -item_endp: /* Expect a value or end bracket/brace */ - skip_whitespace(&s); - if (*s == ']' || *s == '}') - goto endp; - goto item; - endp: /* Handle an end bracket/brace */ if (*s != end_parenthesis(parent)) goto failed; @@ -490,9 +539,12 @@ endp: /* Handle an end bracket/brace */ /* The other pointers were set when we started parsing this node in the 'item' state. */ - node->orig.value_end = s; + node->orig.value.end = s; + node->orig.right_space.start = s; + skip_whitespace(&s); - node->orig.end = s; + + node->orig.right_space.end = s; if (parent) goto comma_endp; @@ -839,63 +891,52 @@ static bool encode_number(String out, const char *string) return true; } +typedef struct { + String str; + bool use_orig; + bool escape_unicode; + bool trim; +} json_encode_ctx; + +static bool json_encode_recurse(json_node *node, json_encode_ctx *ctx); + char *json_encode(json_node *node, int options) { - String (ret); - const char *txt; - json_node *root; - bool use_orig_opt = !!(options & JSONOPT_USE_ORIG); - bool escape_unicode = !!(options & JSONOPT_ESCAPE_UNICODE); - - #define use_orig \ - (use_orig_opt && node->orig.used) - #define use_orig_unless(FLAG) \ - (use_orig_opt && node->orig.used && !node->orig.FLAG) - - #define push_orig(START, END) \ - string_append_range(ret, node->orig.START, node->orig.END) - - if (!node) + json_encode_ctx ctx = { + NewString(), + !!(options & JSONOPT_USE_ORIG), + !!(options & JSONOPT_ESCAPE_UNICODE), + !(options & JSONOPT_NO_TRIM)}; + + if (!json_encode_recurse(node, &ctx)) { + string_free(ctx.str); return NULL; - root = node; + } - goto begin_skip_key; - -begin: /* Encode entire node, or (if it's an array or object) - the beginning of it. */ - - if (node->key) { - if (use_orig) - push_orig(start, key_start); + return string_buffer(ctx.str); +} - if (use_orig_unless(key_changed)) { - push_orig(key_start, key_end); - } else { - if (!encode_string(ret, node->key, node->key_length, '"', escape_unicode)) - goto failed; - } +static bool json_encode_recurse(json_node *node, json_encode_ctx *ctx) +{ + #define has_orig(field) \ + (use_orig && node->orig.field.start) + #define push_orig(field) \ + string_append_range(ctx->str, node->orig.field.start, node->orig.field.end) - if (use_orig) - push_orig(key_end, value_start); - else - string_append_char(ret, ':'); - } else { - if (use_orig) - push_orig(start, value_start); - } + bool use_orig = ctx->use_orig; + bool trim = ctx->trim; -begin_skip_key: + ctx->trim = false; /* Don't trim internal nodes, just the root node. */ - if (use_orig_unless(value_changed)) { - /* if the node or its children haven't been modified, - * and JSONOPT_USE_ORIG has been specified, then the entire - * original node string (other than trailing space) - * will be emitted by this one statement. */ - push_orig(value_start, value_end); + if (!trim && has_orig(left_space)) + push_orig(left_space); - goto finish_skip_bracket; + if (has_orig(value)) { + push_orig(value); } else { - txt = NULL; + const char *txt = NULL; + json_node *child; + switch (node->type) { case JSON_NULL: txt = "null"; @@ -907,72 +948,78 @@ begin_skip_key: txt = "false"; break; case JSON_STRING: - if (!encode_string( ret, + if (!encode_string( ctx->str, node->v.string.str, node->v.string.length, '"', - escape_unicode)) - goto failed; + ctx->escape_unicode)) + return false; break; case JSON_NUMBER: - if (!encode_number(ret, node->v.number)) - goto failed; + if (!encode_number(ctx->str, node->v.number)) + return false; break; case JSON_ARRAY: - txt = "["; + string_append_char(ctx->str, '['); + + json_foreach(child, node) { + json_encode_recurse(child, ctx); + if (child->next) + string_append_char(ctx->str, ','); + } + + string_append_char(ctx->str, ']'); break; case JSON_OBJECT: - txt = "{"; + string_append_char(ctx->str, '{'); + + json_foreach(child, node) { + /* Shadows the parent node (assigned to the variable @node) + * so we can use our macros on the child node instead. + * Hurray for lexical scoping! */ + json_node *node = child; + + if (has_orig(key_left_space)) + push_orig(key_left_space); + + if (has_orig(key)) { + push_orig(key); + } else { + if (!encode_string( ctx->str, + node->key, + node->key_length, + '"', + ctx->escape_unicode)) + return false; + } + + if (has_orig(key_right_space)) + push_orig(key_right_space); + + string_append_char(ctx->str, ':'); + + json_encode_recurse(node, ctx); + + if (node->next) + string_append_char(ctx->str, ','); + } + + string_append_char(ctx->str, '}'); break; default: - goto failed; + return false; } + if (txt) - string_append(ret, txt); - - if (is_internal(node) && node->v.children.head) { - node = node->v.children.head; - goto begin; - } else { - goto finish; - } + string_append(ctx->str, txt); } - -finish: /* Finish a node and move to the next one. */ - if (node->type == JSON_ARRAY) - string_append_char(ret, ']'); - else if (node->type == JSON_OBJECT) - string_append_char(ret, '}'); - -finish_skip_bracket: - if (node == root) - goto end; - - if (use_orig) - push_orig(value_end, end); - if (node->next) { - string_append_char(ret, ','); - node = node->next; - goto begin; - } - if (node->parent) { - node = node->parent; - goto finish; - } else { - Assert(false); - } - goto end; + if (!trim && has_orig(right_space)) + push_orig(right_space); -end: /* All nodes finished being serialized. */ - return string_buffer(ret); - -failed: /* Handle error. */ - string_free(ret); - return NULL; + return true; - #undef use_orig - #undef use_orig_unless + #undef has_orig #undef push_orig } |
