lib: json: Parse nested objects and arrays

Parse arrays and nested objects.

Array parsing is limited to items of the same type, and requires an array
with fixed number of elements.  Elements can be of any type supported by
the parser, including arrays and objects.

The return value of json_obj_parse() won't be that helpful: the nth bit
will only be set if the object has been fully decoded.

Jira: ZEP-1607
Change-Id: I472e402ae3f36a1bd1505decc0313f74cbfa2e07
Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
This commit is contained in:
Leandro Pereira 2017-03-21 13:51:15 -07:00 committed by Anas Nashif
parent 0505c9c881
commit 67ac6f6701
2 changed files with 270 additions and 67 deletions

View file

@ -263,6 +263,8 @@ static void *lexer_json(struct lexer *lexer)
return NULL;
case '}':
case '{':
case '[':
case ']':
case ',':
case ':':
emit(lexer, (enum json_tokens)chr);
@ -321,7 +323,23 @@ static int obj_init(struct json_obj *json, char *data, size_t len)
return 0;
}
static int obj_next(struct json_obj *json, struct json_obj_key_value *kv)
static int element_token(enum json_tokens token)
{
switch (token) {
case JSON_TOK_OBJECT_START:
case JSON_TOK_LIST_START:
case JSON_TOK_STRING:
case JSON_TOK_NUMBER:
case JSON_TOK_TRUE:
case JSON_TOK_FALSE:
return 0;
default:
return -EINVAL;
}
}
static int obj_next(struct json_obj *json,
struct json_obj_key_value *kv)
{
struct token token;
@ -369,16 +387,26 @@ static int obj_next(struct json_obj *json, struct json_obj_key_value *kv)
return -EINVAL;
}
switch (kv->value.type) {
case JSON_TOK_STRING:
case JSON_TOK_NUMBER:
case JSON_TOK_TRUE:
case JSON_TOK_FALSE:
case JSON_TOK_NULL:
return 0;
default:
return element_token(kv->value.type);
}
static int arr_next(struct json_obj *json, struct token *value)
{
if (!lexer_next(&json->lexer, value)) {
return -EINVAL;
}
if (value->type == JSON_TOK_LIST_END) {
return 0;
}
if (value->type == JSON_TOK_COMMA) {
if (!lexer_next(&json->lexer, value)) {
return -EINVAL;
}
}
return element_token(value->type);
}
static int decode_num(const struct token *token, int32_t *num)
@ -418,34 +446,131 @@ static bool equivalent_types(enum json_tokens type1, enum json_tokens type2)
return type1 == type2;
}
int json_obj_parse(char *payload, size_t len,
const struct json_obj_descr *descr, size_t descr_len,
void *val)
static int obj_parse(struct json_obj *obj,
const struct json_obj_descr *descr, size_t descr_len,
void *val);
static int arr_parse(struct json_obj *obj,
const struct json_obj_descr *elem_descr,
size_t max_elements, void *field, void *val);
static int decode_value(struct json_obj *obj,
const struct json_obj_descr *descr,
struct token *value, void *field, void *val)
{
if (!equivalent_types(value->type, descr->type)) {
return -EINVAL;
}
switch (descr->type) {
case JSON_TOK_OBJECT_START:
return obj_parse(obj, descr->sub_descr,
descr->sub_descr_len,
field);
case JSON_TOK_LIST_START:
return arr_parse(obj, descr->element_descr,
descr->n_elements, field, val);
case JSON_TOK_FALSE:
case JSON_TOK_TRUE: {
bool *value = field;
*value = descr->type == JSON_TOK_TRUE;
return 0;
}
case JSON_TOK_NUMBER: {
int32_t *num = field;
return decode_num(value, num);
}
case JSON_TOK_STRING: {
char **str = field;
*value->end = '\0';
*str = value->start;
return 0;
}
default:
return -EINVAL;
}
}
static ptrdiff_t get_elem_size(const struct json_obj_descr *descr)
{
switch (descr->type) {
case JSON_TOK_NUMBER:
return sizeof(int32_t);
case JSON_TOK_STRING:
return sizeof(char *);
case JSON_TOK_TRUE:
case JSON_TOK_FALSE:
return sizeof(bool);
case JSON_TOK_LIST_START:
return descr->n_elements * get_elem_size(descr->element_descr);
case JSON_TOK_OBJECT_START: {
ptrdiff_t total = 0;
size_t i;
for (i = 0; i < descr->sub_descr_len; i++) {
total += get_elem_size(&descr->sub_descr[i]);
}
return total;
}
default:
return -EINVAL;
}
}
static int arr_parse(struct json_obj *obj,
const struct json_obj_descr *elem_descr,
size_t max_elements, void *field, void *val)
{
ptrdiff_t elem_size = get_elem_size(elem_descr);
void *last_elem = (char *)field + elem_size * max_elements;
size_t *elements = (size_t *)((char *)val + elem_descr->offset);
struct token value;
assert(elem_size > 0);
*elements = 0;
while (!arr_next(obj, &value)) {
if (value.type == JSON_TOK_LIST_END) {
return 0;
}
if (decode_value(obj, elem_descr, &value, field, val) < 0) {
return -EINVAL;
}
(*elements)++;
field = (char *)field + elem_size;
if (field == last_elem) {
return -ENOSPC;
}
}
return -EINVAL;
}
static int obj_parse(struct json_obj *obj, const struct json_obj_descr *descr,
size_t descr_len, void *val)
{
struct json_obj obj;
struct json_obj_key_value kv;
int32_t decoded_fields = 0;
size_t i;
int ret;
assert(descr_len < (sizeof(decoded_fields) * CHAR_BIT - 1));
ret = obj_init(&obj, payload, len);
if (ret < 0) {
return ret;
}
while (!obj_next(&obj, &kv)) {
while (!obj_next(obj, &kv)) {
if (kv.value.type == JSON_TOK_OBJECT_END) {
if (decoded_fields == (1 << descr_len) - 1) {
return decoded_fields;
}
return -EINVAL;
return decoded_fields;
}
for (i = 0; i < descr_len; i++) {
void *field = (char *)val + descr[i].offset;
void *decode_field = (char *)val + descr[i].offset;
/* Field has been decoded already, skip */
if (decoded_fields & (1 << i)) {
@ -458,53 +583,42 @@ int json_obj_parse(char *payload, size_t len,
}
if (memcmp(kv.key, descr[i].field_name,
descr[i].field_name_len)) {
descr[i].field_name_len)) {
continue;
}
/* Is the value of the expected type? */
if (!equivalent_types(kv.value.type, descr[i].type)) {
return -EINVAL;
}
/* Store the decoded value */
switch (descr[i].type) {
case JSON_TOK_FALSE:
case JSON_TOK_TRUE: {
bool *value = field;
*value = descr[i].type == JSON_TOK_TRUE;
break;
}
case JSON_TOK_NUMBER: {
int32_t *num = field;
if (decode_num(&kv.value, num) < 0) {
return -EINVAL;
}
break;
}
case JSON_TOK_STRING: {
char **str = field;
*kv.value.end = '\0';
*str = kv.value.start;
break;
}
default:
return -EINVAL;
ret = decode_value(obj, &descr[i], &kv.value,
decode_field, val);
if (ret < 0) {
return ret;
}
decoded_fields |= 1<<i;
break;
}
}
return -EINVAL;
}
int json_obj_parse(char *payload, size_t len,
const struct json_obj_descr *descr, size_t descr_len,
void *val)
{
struct json_obj obj;
int ret;
assert(descr_len < (sizeof(ret) * CHAR_BIT - 1));
ret = obj_init(&obj, payload, len);
if (ret < 0) {
return ret;
}
return obj_parse(&obj, descr, descr_len, val);
}
static const char escapable[] = "\"\\/\b\f\n\r\t";
static int json_escape_internal(char *str, size_t *len, size_t buf_size)

View file

@ -7,6 +7,7 @@
#ifndef __JSON_H
#define __JSON_H
#include <misc/util.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
@ -15,6 +16,8 @@ enum json_tokens {
JSON_TOK_NONE = '_',
JSON_TOK_OBJECT_START = '{',
JSON_TOK_OBJECT_END = '}',
JSON_TOK_LIST_START = '[',
JSON_TOK_LIST_END = ']',
JSON_TOK_STRING = '"',
JSON_TOK_COLON = ':',
JSON_TOK_COMMA = ',',
@ -32,11 +35,96 @@ struct json_obj_descr {
size_t offset;
/* Valid values here: JSON_TOK_STRING, JSON_TOK_NUMBER,
* JSON_TOK_TRUE, JSON_TOK_FALSE. (All others ignored.)
* JSON_TOK_TRUE, JSON_TOK_FALSE, JSON_TOK_OBJECT_START,
* JSON_TOK_LIST_START. (All others ignored.)
*/
enum json_tokens type;
union {
struct {
const struct json_obj_descr *sub_descr;
size_t sub_descr_len;
};
struct {
const struct json_obj_descr *element_descr;
size_t n_elements;
};
};
};
/**
* @brief Helper macro to declare a descriptor for an object value
*
* @param struct_ Struct packing the values
*
* @param field_name_ Field name in the struct
*
* @param sub_descr_ Array of json_obj_descr describing the subobject
*
* Here's an example of use:
* struct nested {
* int foo;
* struct {
* int baz;
* } bar;
* };
*
* struct json_obj_descr nested_bar[] = {
* { ... declare bar.baz descriptor ... },
* };
* struct json_obj_descr nested[] = {
* { ... declare foo descriptor ... },
* JSON_OBJ_DESCR_OBJECT(struct nested, bar, nested_bar),
* };
*/
#define JSON_OBJ_DESCR_OBJECT(struct_, field_name_, sub_descr_) \
{ \
.field_name = (#field_name_), \
.field_name_len = (sizeof(#field_name_) - 1), \
.offset = offsetof(struct_, field_name_), \
.type = JSON_TOK_OBJECT_START, \
.sub_descr = sub_descr_, \
.sub_descr_len = ARRAY_SIZE(sub_descr_) \
}
/**
* @brief Helper macro to declare a descriptor for an array value
*
* @param struct_ Struct packing the values
*
* @param field_name_ Field name in the struct
*
* @param max_len_ Maximum number of elements in array
*
* @param len_field_ Field name in the struct for the number of elements
* in the array
*
* @param elem_type_ Element type
*
* Here's an example of use:
* struct example {
* int foo[10];
* size_t foo_len;
* };
*
* struct json_obj_descr array[] = {
* JSON_OBJ_DESCR_ARRAY(struct example, foo, JSON_TOK_NUMBER)
* };
*/
#define JSON_OBJ_DESCR_ARRAY(struct_, field_name_, max_len_, \
len_field_, elem_type_) \
{ \
.field_name = (#field_name_), \
.field_name_len = sizeof(#field_name_) - 1, \
.offset = offsetof(struct_, field_name_), \
.type = JSON_TOK_LIST_START, \
.element_descr = &(struct json_obj_descr) { \
.type = elem_type_, \
.offset = offsetof(struct_, len_field_), \
}, \
.n_elements = (max_len_), \
}
/**
* @brief Parses the JSON-encoded object pointer to by @param json, with
* size @param len, according to the descriptor pointed to by @param descr.
@ -55,11 +143,12 @@ struct json_obj_descr {
* .type = JSON_TOK_STRING }
* };
*
* Since this parser is designed for machine-to-machine communications,
* some liberties were taken to simplify the design: (1) strings are not
* unescaped; (2) no UTF-8 validation is performed; (3) only integer
* numbers are supported; (4) nested objects are not supported, including
* arrays and objects within objects.
* Since this parser is designed for machine-to-machine communications, some
* liberties were taken to simplify the design:
* (1) strings are not unescaped (but only valid escape sequences are
* accepted);
* (2) no UTF-8 validation is performed; and
* (3) only integer numbers are supported (no strtod() in the minimal libc).
*
* @param json Pointer to JSON-encoded value to be parsed
*