summaryrefslogtreecommitdiff
path: root/json.c
diff options
context:
space:
mode:
authorJoey Adams2010-07-22 16:00:15 +0000
committerJoey Adams2010-07-22 16:00:15 +0000
commit9dd13c566fe14be9d3c807e9d4e140c5a9c6ad9d (patch)
tree45dff16951a1d7f1724a733d4dacfdbf50dd496f /json.c
parentabf3491883d45ce492c1d1a06c3fd01c2e8f90d8 (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.c339
1 files changed, 193 insertions, 146 deletions
diff --git a/json.c b/json.c
index 23c8282..783cb84 100644
--- a/json.c
+++ b/json.c
@@ -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
}