From 49fc708d4c5aeebad570f59a588f7520a4c62f78 Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Sun, 15 May 2011 13:57:48 +0300 Subject: [PATCH] Add JSON_REJECT_DUPLICATES decoding flag With this flag, a decoding error is issued if any JSON object in the input contains duplicate keys. Fixes GH-3. --- doc/apiref.rst | 29 +++++++++++++++++++++-------- src/jansson.h | 7 ++++++- src/load.c | 39 +++++++++++++++++++++------------------ test/suites/api/test_load.c | 17 ++++++++++++++++- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/doc/apiref.rst b/doc/apiref.rst index ea1877d..6ab7a6f 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -780,14 +780,28 @@ See :ref:`rfc-conformance` for a discussion on Jansson's conformance to the JSON specification. It explains many design decisions that affect especially the behavior of the decoder. +Each function takes a *flags* parameter that can be used to control +the behavior of the decoder. Its default value is 0. The following +macros can be ORed together to obtain *flags*. + +``JSON_REJECT_DUPLICATES`` + Issue a decoding error if any JSON object in the input text + contains duplicate keys. Without this flag, the value of the last + occurence of each key ends up in the result. Key equivalence is + checked byte-by-byte, without special Unicode comparison + algorithms. + + .. versionadded:: 2.1 + +The following functions perform the actual JSON decoding. + .. function:: json_t *json_loads(const char *input, size_t flags, json_error_t *error) .. refcounting:: new Decodes the JSON string *input* and returns the array or object it contains, or *NULL* on error, in which case *error* is filled with - information about the error. *flags* is currently unused, and - should be set to 0. + information about the error. *flags* is described above. .. function:: json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error) @@ -797,8 +811,7 @@ affect especially the behavior of the decoder. returns the array or object it contains, or *NULL* on error, in which case *error* is filled with information about the error. This is similar to :func:`json_loads()` except that the string doesn't - need to be null-terminated. *flags* is currently unused, and should - be set to 0. + need to be null-terminated. *flags* is described above. .. versionadded:: 2.1 @@ -808,8 +821,8 @@ affect especially the behavior of the decoder. Decodes the JSON text in stream *input* and returns the array or object it contains, or *NULL* on error, in which case *error* is - filled with information about the error. *flags* is currently - unused, and should be set to 0. + filled with information about the error. *flags* is described + above. .. function:: json_t *json_load_file(const char *path, size_t flags, json_error_t *error) @@ -817,8 +830,8 @@ affect especially the behavior of the decoder. Decodes the JSON text in file *path* and returns the array or object it contains, or *NULL* on error, in which case *error* is - filled with information about the error. *flags* is currently - unused, and should be set to 0. + filled with information about the error. *flags* is described + above. .. _apiref-pack: diff --git a/src/jansson.h b/src/jansson.h index 2a0860e..9bdbb11 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -214,13 +214,18 @@ json_t *json_copy(json_t *value); json_t *json_deep_copy(json_t *value); -/* loading, printing */ +/* decoding */ + +#define JSON_REJECT_DUPLICATES 0x1 json_t *json_loads(const char *input, size_t flags, json_error_t *error); json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error); json_t *json_loadf(FILE *input, size_t flags, json_error_t *error); json_t *json_load_file(const char *path, size_t flags, json_error_t *error); + +/* encoding */ + #define JSON_INDENT(n) (n & 0x1F) #define JSON_COMPACT 0x20 #define JSON_ENSURE_ASCII 0x40 diff --git a/src/load.c b/src/load.c index 9933583..ba00386 100644 --- a/src/load.c +++ b/src/load.c @@ -642,9 +642,9 @@ static void lex_close(lex_t *lex) /*** parser ***/ -static json_t *parse_value(lex_t *lex, json_error_t *error); +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error); -static json_t *parse_object(lex_t *lex, json_error_t *error) +static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error) { json_t *object = json_object(); if(!object) @@ -667,6 +667,14 @@ static json_t *parse_object(lex_t *lex, json_error_t *error) if(!key) return NULL; + if(flags & JSON_REJECT_DUPLICATES) { + if(json_object_get(object, key)) { + jsonp_free(key); + error_set(error, lex, "duplicate object key"); + goto error; + } + } + lex_scan(lex, error); if(lex->token != ':') { jsonp_free(key); @@ -675,7 +683,7 @@ static json_t *parse_object(lex_t *lex, json_error_t *error) } lex_scan(lex, error); - value = parse_value(lex, error); + value = parse_value(lex, flags, error); if(!value) { jsonp_free(key); goto error; @@ -709,7 +717,7 @@ error: return NULL; } -static json_t *parse_array(lex_t *lex, json_error_t *error) +static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error) { json_t *array = json_array(); if(!array) @@ -720,7 +728,7 @@ static json_t *parse_array(lex_t *lex, json_error_t *error) return array; while(lex->token) { - json_t *elem = parse_value(lex, error); + json_t *elem = parse_value(lex, flags, error); if(!elem) goto error; @@ -749,7 +757,7 @@ error: return NULL; } -static json_t *parse_value(lex_t *lex, json_error_t *error) +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error) { json_t *json; @@ -782,11 +790,11 @@ static json_t *parse_value(lex_t *lex, json_error_t *error) break; case '{': - json = parse_object(lex, error); + json = parse_object(lex, flags, error); break; case '[': - json = parse_array(lex, error); + json = parse_array(lex, flags, error); break; case TOKEN_INVALID: @@ -804,7 +812,7 @@ static json_t *parse_value(lex_t *lex, json_error_t *error) return json; } -static json_t *parse_json(lex_t *lex, json_error_t *error) +static json_t *parse_json(lex_t *lex, size_t flags, json_error_t *error) { lex_scan(lex, error); if(lex->token != '[' && lex->token != '{') { @@ -812,7 +820,7 @@ static json_t *parse_json(lex_t *lex, json_error_t *error) return NULL; } - return parse_value(lex, error); + return parse_value(lex, flags, error); } typedef struct @@ -841,8 +849,6 @@ json_t *json_loads(const char *string, size_t flags, json_error_t *error) json_t *result; string_data_t stream_data; - (void)flags; /* unused */ - stream_data.data = string; stream_data.pos = 0; @@ -851,7 +857,7 @@ json_t *json_loads(const char *string, size_t flags, json_error_t *error) jsonp_error_init(error, ""); - result = parse_json(&lex, error); + result = parse_json(&lex, flags, error); if(!result) goto out; @@ -892,8 +898,6 @@ json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t json_t *result; buffer_data_t stream_data; - (void)flags; /* unused */ - stream_data.data = buffer; stream_data.pos = 0; stream_data.len = buflen; @@ -903,7 +907,7 @@ json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t jsonp_error_init(error, ""); - result = parse_json(&lex, error); + result = parse_json(&lex, flags, error); if(!result) goto out; @@ -924,7 +928,6 @@ json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) lex_t lex; const char *source; json_t *result; - (void)flags; /* unused */ if(lex_init(&lex, (get_func)fgetc, input)) return NULL; @@ -936,7 +939,7 @@ json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) jsonp_error_init(error, source); - result = parse_json(&lex, error); + result = parse_json(&lex, flags, error); if(!result) goto out; diff --git a/test/suites/api/test_load.c b/test/suites/api/test_load.c index ecb46d8..83143fb 100644 --- a/test/suites/api/test_load.c +++ b/test/suites/api/test_load.c @@ -9,7 +9,7 @@ #include #include "util.h" -int main() +static void file_not_found() { json_t *json; json_error_t error; @@ -21,6 +21,21 @@ int main() fail("json_load_file returned an invalid line number"); if(strcmp(error.text, "unable to open /path/to/nonexistent/file.json: No such file or directory") != 0) fail("json_load_file returned an invalid error message"); +} + +static void reject_duplicates() +{ + json_error_t error; + + if(json_loads("{\"foo\": 1, \"foo\": 2}", JSON_REJECT_DUPLICATES, &error)) + fail("json_loads did not detect a duplicate key"); + check_error("duplicate object key near '\"foo\"'", "", 1, 16, 16); +} + +int main() +{ + file_not_found(); + reject_duplicates(); return 0; } -- 2.1.4