summaryrefslogtreecommitdiff
path: root/json.c
diff options
context:
space:
mode:
authorJoey Adams2010-06-21 23:52:23 +0000
committerJoey Adams2010-06-21 23:52:23 +0000
commitb153d76442ba1ef1237c4cc36f491b3aff7b8510 (patch)
treed3d4f0cd8d6d2be370a98b0d13e80dafc6f60bb5 /json.c
parentf1851e0b1fd7f002b4033d9717ab9a704d402d48 (diff)
* Made it so json_path preserves original text.
* Removed the global variable json_escape_unicode and added a parameter for it to the json_encode and json_encode_string C functions.
Diffstat (limited to 'json.c')
-rw-r--r--json.c219
1 files changed, 150 insertions, 69 deletions
diff --git a/json.c b/json.c
index 817f57f..ade9eeb 100644
--- a/json.c
+++ b/json.c
@@ -26,9 +26,6 @@
#include <ctype.h>
-
-bool json_escape_unicode = false;
-
#define JSON_malloc palloc
/* repalloc and pfree can't take a null pointer, unlike normal realloc and free. */
@@ -165,6 +162,10 @@ static inline void string_append(String str, const char *append)
{
string_append_length(str, append, strlen(append));
}
+static inline void string_append_range(String str, const char *start, const char *end)
+{
+ string_append_length(str, start, end - start);
+}
static inline void string_append_char(String str, char c)
{
*string_grow(str, 1) = c;
@@ -369,6 +370,10 @@ json_node *json_decode(const char *str)
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;
if (!str)
return NULL;
@@ -379,11 +384,14 @@ json_node *json_decode(const char *str)
goto item;
item: /* Expect a value */
+ start = s;
skip_whitespace(&s);
if (parent && parent->type == JSON_OBJECT) {
/* Parse member key string. */
+ key_start = s;
key = json_decode_string(&s, &key_length, true);
+ key_end = s;
if (!key)
goto failed;
@@ -396,7 +404,15 @@ item: /* Expect a value */
} 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.
+ */
+
+ value_start = s;
node = decode_leaf(&s);
if (!node) {
if (*s == '[')
@@ -406,12 +422,28 @@ item: /* Expect a value */
else
goto failed;
s++;
+
+ /* node->orig.value_end is dangling (actually NULL) for now,
+ but will be initialized when we get to state 'endp' . */
+ } else {
+ node->orig.value_end = s;
+ skip_whitespace(&s);
+ node->orig.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;
+
+ /* 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;
}
if (parent)
@@ -420,6 +452,8 @@ item: /* Expect a value */
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. */
parent = node;
goto item_endp;
}
@@ -430,8 +464,6 @@ item: /* Expect a value */
goto end;
comma_endp: /* Expect a comma or end bracket/brace */
- skip_whitespace(&s);
-
if (*s == ',') {
s++;
goto item;
@@ -451,15 +483,23 @@ endp: /* Handle an end bracket/brace */
if (*s != end_parenthesis(parent))
goto failed;
s++;
+
+ /* "pop" a node from the "stack" */
node = parent;
parent = parent->parent;
+
+ /* The other pointers were set when we started
+ parsing this node in the 'item' state. */
+ node->orig.value_end = s;
+ skip_whitespace(&s);
+ node->orig.end = s;
+
if (parent)
goto comma_endp;
else
goto end;
end: /* Expect end of text */
- skip_whitespace(&s);
if (*s)
goto failed;
return node;
@@ -706,7 +746,7 @@ json_type json_text_type(const char *str, size_t nbytes)
/****************************** Encoding *****************************/
-static bool encode_string(String out, const char *string, size_t length, char quote)
+static bool encode_string(String out, const char *string, size_t length, char quote, bool escape_unicode)
{
const char *s = string;
const char *e = s + length;
@@ -732,7 +772,7 @@ static bool encode_string(String out, const char *string, size_t length, char qu
e = quote;
break;
}
- if (c < 0x1F || (c >= 0x80 && json_escape_unicode)) {
+ if (c < 0x1F || (c >= 0x80 && escape_unicode)) {
/* Encode using \u.... */
unsigned int uc, lc;
char txt[13];
@@ -799,66 +839,103 @@ static bool encode_number(String out, const char *string)
return true;
}
-char *json_encode(json_node *node)
+char *json_encode(json_node *node, int options)
{
- String(ret);
- const char *txt;
- json_node *root;
+ 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)
return NULL;
root = node;
-
- goto begin_nokey;
+
+ goto begin_skip_key;
begin: /* Encode entire node, or (if it's an array or object)
the beginning of it. */
if (node->key) {
- if (!encode_string(ret, node->key, node->key_length, '"'))
- goto failed;
- string_append_char(ret, ':');
- }
- goto begin_nokey;
-
-begin_nokey:
-
- txt = NULL;
- switch (node->type) {
- case JSON_NULL:
- txt = "null";
- break;
- case JSON_BOOL:
- if (node->v.v_bool)
- txt = "true";
- else
- txt = "false";
- break;
- case JSON_STRING:
- if (!encode_string(ret, node->v.string.str, node->v.string.length, '"'))
- goto failed;
- break;
- case JSON_NUMBER:
- if (!encode_number(ret, node->v.number))
+ if (use_orig)
+ push_orig(start, key_start);
+
+ 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;
- break;
- case JSON_ARRAY:
- txt = "[";
- break;
- case JSON_OBJECT:
- txt = "{";
- break;
- default:
- goto failed;
+ }
+
+ if (use_orig)
+ push_orig(key_end, value_start);
+ else
+ string_append_char(ret, ':');
+ } else {
+ if (use_orig)
+ push_orig(start, value_start);
}
- if (txt)
- string_append(ret, txt);
-
- if (is_internal(node) && node->v.children.head) {
- node = node->v.children.head;
- goto begin;
+
+begin_skip_key:
+
+ 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);
+
+ goto finish_skip_bracket;
} else {
- goto finish;
+ txt = NULL;
+ switch (node->type) {
+ case JSON_NULL:
+ txt = "null";
+ break;
+ case JSON_BOOL:
+ if (node->v.v_bool)
+ txt = "true";
+ else
+ txt = "false";
+ break;
+ case JSON_STRING:
+ if (!encode_string( ret,
+ node->v.string.str,
+ node->v.string.length,
+ '"',
+ escape_unicode))
+ goto failed;
+ break;
+ case JSON_NUMBER:
+ if (!encode_number(ret, node->v.number))
+ goto failed;
+ break;
+ case JSON_ARRAY:
+ txt = "[";
+ break;
+ case JSON_OBJECT:
+ txt = "{";
+ break;
+ default:
+ goto failed;
+ }
+ if (txt)
+ string_append(ret, txt);
+
+ if (is_internal(node) && node->v.children.head) {
+ node = node->v.children.head;
+ goto begin;
+ } else {
+ goto finish;
+ }
}
finish: /* Finish a node and move to the next one. */
@@ -867,9 +944,13 @@ finish: /* Finish a node and move to the next one. */
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;
@@ -889,13 +970,17 @@ end: /* All nodes finished being serialized. */
failed: /* Handle error. */
string_free(ret);
return NULL;
+
+ #undef use_orig
+ #undef use_orig_unless
+ #undef push_orig
}
-char *json_encode_string(const char *str, size_t length, char quote)
+char *json_encode_string(const char *str, size_t length, char quote, bool escape_unicode)
{
String(ret);
- if (!encode_string(ret, str, length, quote)) {
+ if (!encode_string(ret, str, length, quote, escape_unicode)) {
string_free(ret);
return NULL;
}
@@ -908,20 +993,16 @@ char *json_encode_string(const char *str, size_t length, char quote)
bool json_validate_liberal(const char *str)
{
- json_node *node = json_decode_liberal(str);
- if (!node)
- return false;
- json_delete(node);
- return true;
-}
+ char *cleaned = json_cleanup(str);
+ json_node *node = json_decode(cleaned);
+ bool ret = !!node;
-json_node *json_decode_liberal(const char *str)
-{
- char *cleaned = json_cleanup(str);
- json_node *node = json_decode(cleaned);
+ if (node)
+ json_delete(node);
if (cleaned)
JSON_free(cleaned);
- return node;
+
+ return ret;
}
char *json_cleanup(const char *str)