Tweak the default validation behaviour of the unpack API
Now, by default, unpacking doesn't check that all array and object items are accessed. The check can be enabled globally by using the JSON_STRICT flag (formerly JSON_UNPACK_ONLY), or on a per-value basis by using the new '!' format character. The '*' format character is still available to disable the global check on a per-value basis.
This commit is contained in:
parent
7706abcbed
commit
ef13fb9189
@ -849,6 +849,7 @@ denotes the C type that is expected as the corresponding argument.
|
|||||||
fourth, etc. format character represent a value. Any value may be
|
fourth, etc. format character represent a value. Any value may be
|
||||||
an object or array, i.e. recursive value building is supported.
|
an object or array, i.e. recursive value building is supported.
|
||||||
|
|
||||||
|
The following functions compose the value building API:
|
||||||
|
|
||||||
.. function:: json_t *json_pack(const char *fmt, ...)
|
.. function:: json_t *json_pack(const char *fmt, ...)
|
||||||
|
|
||||||
@ -896,10 +897,10 @@ and extract, or *unpack*, data from them. Like :ref:`building values
|
|||||||
|
|
||||||
While a JSON value is unpacked, the type specified in the format
|
While a JSON value is unpacked, the type specified in the format
|
||||||
string is checked to match that of the JSON value. This is the
|
string is checked to match that of the JSON value. This is the
|
||||||
validation part of the process. By default, the unpacking functions
|
validation part of the process. In addition to this, the unpacking
|
||||||
also check that all items of arrays and objects are unpacked. This
|
functions can also check that all items of arrays and objects are
|
||||||
check be disabled with the format character ``*`` or by using the flag
|
unpacked. This check be enabled with the format character ``!`` or by
|
||||||
``JSON_UNPACK_ONLY``.
|
using the flag ``JSON_STRICT``. See below for details.
|
||||||
|
|
||||||
Here's the full list of format characters. The type in parentheses
|
Here's the full list of format characters. The type in parentheses
|
||||||
denotes the JSON type, and the type in brackets (if any) denotes the C
|
denotes the JSON type, and the type in brackets (if any) denotes the C
|
||||||
@ -951,12 +952,21 @@ type whose address should be passed.
|
|||||||
``fmt`` may contain objects and arrays as values, i.e. recursive
|
``fmt`` may contain objects and arrays as values, i.e. recursive
|
||||||
value extraction is supporetd.
|
value extraction is supporetd.
|
||||||
|
|
||||||
``*``
|
``!``
|
||||||
This special format character is used to disable the check that
|
This special format character is used to enable the check that
|
||||||
all object and array items are accessed on a per-value basis. It
|
all object and array items are accessed, on a per-value basis. It
|
||||||
must appear inside an array or object as the last format character
|
must appear inside an array or object as the last format character
|
||||||
before the closing bracket or brace.
|
before the closing bracket or brace. To enable the check globally,
|
||||||
|
use the ``JSON_STRICT`` unpacking flag.
|
||||||
|
|
||||||
|
``*``
|
||||||
|
This special format character is the opposite of ``!``. If the
|
||||||
|
``JSON_STRICT`` flag is used, ``*`` can be used to disable the
|
||||||
|
strict check on a per-value basis. It must appear inside an array
|
||||||
|
or object as the last format character before the closing bracket
|
||||||
|
or brace.
|
||||||
|
|
||||||
|
The following functions compose the parsing and validation API:
|
||||||
|
|
||||||
.. function:: int json_unpack(json_t *root, const char *fmt, ...)
|
.. function:: int json_unpack(json_t *root, const char *fmt, ...)
|
||||||
|
|
||||||
@ -974,11 +984,11 @@ type whose address should be passed.
|
|||||||
|
|
||||||
The following unpacking flags are available:
|
The following unpacking flags are available:
|
||||||
|
|
||||||
``JSON_UNPACK_ONLY``
|
``JSON_STRICT``
|
||||||
Disable the validation step checking that all object and array
|
Enable the extra validation step checking that all object and
|
||||||
items are unpacked. This is equivalent to appending the format
|
array items are unpacked. This is equivalent to appending the
|
||||||
character ``*`` to the end of every array and object in the format
|
format character ``!`` to the end of every array and object in the
|
||||||
string.
|
format string.
|
||||||
|
|
||||||
``JSON_VALIDATE_ONLY``
|
``JSON_VALIDATE_ONLY``
|
||||||
Don't extract any data, just validate the JSON value against the
|
Don't extract any data, just validate the JSON value against the
|
||||||
|
@ -195,7 +195,7 @@ json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...);
|
|||||||
json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap);
|
json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap);
|
||||||
|
|
||||||
#define JSON_VALIDATE_ONLY 0x1
|
#define JSON_VALIDATE_ONLY 0x1
|
||||||
#define JSON_UNPACK_ONLY 0x2
|
#define JSON_STRICT 0x2
|
||||||
|
|
||||||
int json_unpack(json_t *root, const char *fmt, ...);
|
int json_unpack(json_t *root, const char *fmt, ...);
|
||||||
int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...);
|
int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...);
|
||||||
|
@ -192,26 +192,23 @@ static int unpack(scanner_t *s, json_t *root, va_list *ap);
|
|||||||
static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
int wildcard = 0;
|
int strict = 0;
|
||||||
|
|
||||||
/* Use a set (emulated by a hashtable) to check that all object
|
/* Use a set (emulated by a hashtable) to check that all object
|
||||||
keys are accessed. Checking that the correct number of keys
|
keys are accessed. Checking that the correct number of keys
|
||||||
were accessed is not enough, as the same key can be unpacked
|
were accessed is not enough, as the same key can be unpacked
|
||||||
multiple times.
|
multiple times.
|
||||||
*/
|
*/
|
||||||
hashtable_t *key_set;
|
hashtable_t key_set;
|
||||||
|
|
||||||
if(!(s->flags & JSON_UNPACK_ONLY)) {
|
if(hashtable_init(&key_set, jsonp_hash_key, jsonp_key_equal, NULL, NULL)) {
|
||||||
key_set = hashtable_create(jsonp_hash_key, jsonp_key_equal, NULL, NULL);
|
set_error(s, "Out of memory");
|
||||||
if(!key_set) {
|
return -1;
|
||||||
set_error(s, "Out of memory");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!json_is_object(root)) {
|
if(!json_is_object(root)) {
|
||||||
set_error(s, "Expected object, got %s", type_name(root));
|
set_error(s, "Expected object, got %s", type_name(root));
|
||||||
goto error;
|
goto out;
|
||||||
}
|
}
|
||||||
next_token(s);
|
next_token(s);
|
||||||
|
|
||||||
@ -219,67 +216,64 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
|||||||
const char *key;
|
const char *key;
|
||||||
json_t *value;
|
json_t *value;
|
||||||
|
|
||||||
if(wildcard) {
|
if(strict != 0) {
|
||||||
set_error(s, "Expected '}' after '*', got '%c'", s->token);
|
set_error(s, "Expected '}' after '%c', got '%c'",
|
||||||
goto error;
|
(strict == 1 ? '!' : '*'), s->token);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!s->token) {
|
if(!s->token) {
|
||||||
set_error(s, "Unexpected end of format string");
|
set_error(s, "Unexpected end of format string");
|
||||||
goto error;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(s->token == '*') {
|
if(s->token == '!' || s->token == '*') {
|
||||||
wildcard = 1;
|
strict = (s->token == '!' ? 1 : -1);
|
||||||
next_token(s);
|
next_token(s);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(s->token != 's') {
|
if(s->token != 's') {
|
||||||
set_error(s, "Expected format 's', got '%c'\n", *s->fmt);
|
set_error(s, "Expected format 's', got '%c'\n", *s->fmt);
|
||||||
goto error;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = va_arg(*ap, const char *);
|
key = va_arg(*ap, const char *);
|
||||||
if(!key) {
|
if(!key) {
|
||||||
set_error(s, "NULL object key");
|
set_error(s, "NULL object key");
|
||||||
goto error;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_token(s);
|
next_token(s);
|
||||||
|
|
||||||
value = json_object_get(root, key);
|
value = json_object_get(root, key);
|
||||||
if(unpack(s, value, ap))
|
if(unpack(s, value, ap))
|
||||||
goto error;
|
goto out;
|
||||||
|
|
||||||
if(!(s->flags & JSON_UNPACK_ONLY))
|
|
||||||
hashtable_set(key_set, (void *)key, NULL);
|
|
||||||
|
|
||||||
|
hashtable_set(&key_set, (void *)key, NULL);
|
||||||
next_token(s);
|
next_token(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(s->flags & JSON_UNPACK_ONLY)
|
if(strict == 0 && (s->flags & JSON_STRICT))
|
||||||
wildcard = 1;
|
strict = 1;
|
||||||
|
|
||||||
if(!wildcard && key_set->size != json_object_size(root)) {
|
if(strict == 1 && key_set.size != json_object_size(root)) {
|
||||||
long diff = (long)json_object_size(root) - (long)key_set->size;
|
long diff = (long)json_object_size(root) - (long)key_set.size;
|
||||||
set_error(s, "%li object items left unpacked", diff);
|
set_error(s, "%li object items left unpacked", diff);
|
||||||
goto error;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
error:
|
out:
|
||||||
if(!(s->flags & JSON_UNPACK_ONLY))
|
hashtable_close(&key_set);
|
||||||
hashtable_destroy(key_set);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
||||||
{
|
{
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
int wildcard = 0;
|
int strict = 0;
|
||||||
|
|
||||||
if(!json_is_array(root)) {
|
if(!json_is_array(root)) {
|
||||||
set_error(s, "Expected array, got %s", type_name(root));
|
set_error(s, "Expected array, got %s", type_name(root));
|
||||||
@ -290,8 +284,10 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
|||||||
while(s->token != ']') {
|
while(s->token != ']') {
|
||||||
json_t *value;
|
json_t *value;
|
||||||
|
|
||||||
if(wildcard) {
|
if(strict != 0) {
|
||||||
set_error(s, "Expected ']' after '*', got '%c'", s->token);
|
set_error(s, "Expected ']' after '%c', got '%c'",
|
||||||
|
(strict == 1 ? '!' : '*'),
|
||||||
|
s->token);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +296,8 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(s->token == '*') {
|
if(s->token == '!' || s->token == '*') {
|
||||||
wildcard = 1;
|
strict = (s->token == '!' ? 1 : -1);
|
||||||
next_token(s);
|
next_token(s);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -319,10 +315,10 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(s->flags & JSON_UNPACK_ONLY)
|
if(strict == 0 && (s->flags & JSON_STRICT))
|
||||||
wildcard = 1;
|
strict = 1;
|
||||||
|
|
||||||
if(!wildcard && i != json_array_size(root)) {
|
if(strict == 1 && i != json_array_size(root)) {
|
||||||
long diff = (long)json_array_size(root) - (long)i;
|
long diff = (long)json_array_size(root) - (long)i;
|
||||||
set_error(s, "%li array items left upacked", diff);
|
set_error(s, "%li array items left upacked", diff);
|
||||||
return -1;
|
return -1;
|
||||||
|
Loading…
Reference in New Issue
Block a user