Parsing JSON
The Thing-IF SDK v2 provides the "jkii" library for parsing JSON. You can use this library, for example, to parse the action parameters in a command.
The library will allow you to extract the value in the specified JSON path with the specified data type (e.g., string and int).
For example, suppose you want to get the value of the second element (i.e., the [1] element in the array) of the color
's rgb
in the next JSON. You can get it by specifying the path /color/rgb/[1]
and saying that you want to get the value in int. The library will return an integer 128 as the returned value.
{
"color": {
"alpha": 255,
"rgb": [
0,
128,
255
]
}
}
In this page, we will briefly present the major features of the jkii library in this page. Please read the jkii Documentation and its source code for the full explanation.
Getting the specific field
We will present some examples of getting the field values from a JSON string with the jkii library.
In the first example, we will show how to get the value of the field key1
in an integer (123) from the JSON {"key1" : 123}
.
const char json_string[] = "{\"key1\" : 123}";
/* jkii resource */
jkii_token_t tokens[8];
jkii_resource_t resource = {tokens, 8};
/* Initialize fields. */
jkii_field_t fields[2];
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/key1";
fields[0].type = JKII_FIELD_TYPE_INTEGER;
fields[1].path = NULL;
/* Parse the JSON string. */
jkii_parse_err_t result = jkii_parse(
json_string,
sizeof(json_string) / sizeof(json_string[0]),
&fields,
&resource);
/* Check if parsing the string failed. */
if (result != JKII_ERR_OK) {
return;
}
/* Check if parsing the field failed. */
if (fields[0].result != JKII_FIELD_ERR_OK ) {
return;
}
/* Write the value of the first field to stdout. Expected output is "value=123". */
printf("value=%d\n", fields[0].field_copy.int_value);
This is what is happening in the sample code:
- Prepare the
jkii_token_t
array and set the array and the number of elements in thejkii_resource_t
so as to make the resource for parsing ready. The required number of elements depends on the data to parse. Make sure to set the sufficient size. - Specify how to parse the fields in the
jkii_field_t
array (See Parsing Instructions to learn how to do this in details). In the sample code, we first zero clear the array and then specify that we want to get the value of the JSON path/key1
as an integer. We then NULL-terminate the array in the second element. To avoid unexpected bugs, please make sure to zero clear the array before you start setting the parsing instruction. - Start parsing by executing the
jkii_parse
function. - Get the parsed result stored in the
field_copy
member of thejkii_field_t
array.
When successful, the function returns JKII_ERR_OK
. Also, the JKII_FIELD_ERR_OK
will be recorded in the result
member of the jkii_field_t
array's field.
Reading multiple fields
If there are multiple fields in the JSON, you can get multiple field values simultaneously by specifying their paths.
Here is an example. This time, we will get the value of the field key1
("abc") and the second element of the array stored in the subfield key2-2
(true
) from the following JSON.
{
"key1": "abc",
"key2": {
"key2-1": 123,
"key2-2": [
false,
true,
false
]
}
}
The sample code for getting the fields is as follows:
const char json_string[] =
"{"
"\"key1\" : \"abc\","
"\"key2\" : {\"key2-1\" : 123, \"key2-2\" : [false, true, false]}"
"}";
char buf[256];
/* jkii resource */
jkii_token_t tokens[32];
jkii_resource_t resource = {tokens, 32};
/* Initialize fields. */
jkii_field_t fields[3];
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/key1";
fields[0].type = JKII_FIELD_TYPE_STRING;
fields[0].field_copy.string = buf;
fields[0].field_copy_buff_size = sizeof(buf) / sizeof(buf[0]);
fields[1].path = "/key2/key2-2/[1]";
fields[1].type = JKII_FIELD_TYPE_BOOLEAN;
fields[2].path = NULL;
/* Parse the JSON string. */
jkii_parse_err_t result = jkii_parse(
json_string,
sizeof(json_string),
&fields,
&resource);
/* Check if parsing the string failed. */
if (result != JKII_ERR_OK) {
return;
}
/* Check if parsing either field failed. */
if (fields[0].result != JKII_FIELD_ERR_OK ||
fields[1].result != JKII_FIELD_ERR_OK) {
return;
}
/* Write the value of the fields to stdout. Expected output is "value1=abc, value2=1". */
printf("value1=%s, value2=%d\n",
fields[0].field_copy.int_value,
fields[1].field_copy.boolean_value);
This is what is happening in the sample code:
- Prepare the
jkii_token_t
array and set the array and the number of elements in thejkii_resource_t
so as to make the resource for parsing ready. The required number of elements depends on the data to parse. Make sure to set the sufficient size. - Specify how to parse fields in the
jkii_field_t
array. In this sample code, we are setting the following instructions:- Get the field
/key1
as a string. By setting the bufferbuf
in the input parameterfield_copy.string
, the API will write the parsed string to this buffer. - Get the target element (the element [1] of the array in the
key2
'skey2-2
) by setting the path/key2/key2-2/[1]
. We assume that we will get a boolean value, so we specify the type as a boolean.
- Get the field
- Start parsing by executing the
jkii_parse
function. - Get the parsed result stored in the
field_copy
member of thejkii_field_t
array. We will get two results this time, so we first check if the parsing the field succeeds, and then get the result.
If the library failed to parse some of the fields, the function returns JKII_ERR_PARTIAL
. An error is recorded in the result
member of the failed field.
Parsing instructions
The parsing instructions are to be set in an array of the jkii_field_t
struct. This section explains how you to set the instructions.
First, declare the jkii_field_t
as an array. The number of elements in the array should be one more than the number of the fields you want to extract. The last element is used for terminating the instruction; please set NULL in its path
member.
Here is a list of fields you need to set. Please zero clear all elements with the memset
function before you start setting the instructions, or you might encounter some unexpected bugs.
Member | Input/Output Parameter |
Description |
---|---|---|
path | Input | The path of the field you want to extract. Please set the path based on the target JSON structure. The path should start with / . Use / as the path separator. You can specify an element of an array with [ ] .For example, please specify the path /light/color/[0] to get 0 from {"light":{"color":[0,128,255]}} . If the field name contains [ , ] , / , or \ , please escape them with \ (e.g., \[ ).
|
result | Output | The status representing the parsing result per field. JKII_FIELD_ERR_OK is set if the parsing was successful. Please check the jkii Documentation - jkii_field_err_t for other possible return values. |
type | Input and Output |
The type of the field. Please set the expected field type as the input. The actual type extracted as the result of the parsing will be set as the output. Please read Data Type for more details on the data type. |
start | Output | The starting position of the parsed JSON string. The position is given relative to the head of the parsed JSON string (the first byte of the JSON string is 0, the next string is 1, and so on). If the parsed value is the string, the position next to " will be given. The position will be returned even if the parsed value was not string. |
end | Output | The finishing position of the parsed JSON string. If the key abc is parsed on {"abc":"XYZ"} , the end will have 5 that points " next to c . The position will be returned even if the parsed value was not string. |
field_copy | Input and Output |
The union with the following members:
|
filed_copy_ buff_size |
Input | The buffer size in bytes. The size needs to be set when you specify the buffer as the input parameter of field_copy.string. Please read Handling String for more details. |
Data type
The chart below summarizes the data types available in jkii library.
In the type
member, set the expected data type of the field specified with the path
member. The field value is extracted if the data type specified matches with the actual data type. If they mismatch, the JKII_FIELD_ERR_TYPE_MISMATCH
error will be recorded in the result
member.
Note that the error will be also recorded when you specify an expected data type (e.g., JKII_FIELD_TYPE_INTEGER
or JKII_FIELD_TYPE_OBJECT
) but the actual JSON field had a null.
If you specify JKII_FIELD_TYPE_ANY
in the type
member, the actual data type extracted will be returned.
Data type | Description |
---|---|
JKII_FIELD_TYPE_ANY | Accept any data type. This can be set as the input. The actual data type of the JSON field (e.g., object, array, or others) will be set as the output. |
JKII_FIELD_TYPE_INTEGER | Represent the int type (for both input and output). You can get the extracted value from the field_copy.int_value . |
JKII_FIELD_TYPE_LONG | Represent the long type (for both input and output). You can get the extracted value from the field_copy.long_value . |
JKII_FIELD_TYPE_DOUBLE | Represent the double type (for both input and output). You get the extracted value from the field_copy.double_value . |
JKII_FIELD_TYPE_BOOLEAN | Represent the boolean type (for both input and output). You get the extracted value from the field_copy.boolean_value . |
JKII_FIELD_TYPE_NULL | Expect the JSON field is null when this data type is set as the input. When you specify KII_JSON_FIELD_TYPE_ANY as the input and null matches in the JSON string, this value will be returned as the output. |
JKII_FIELD_TYPE_STRING | Represent the string type (both input and output). You can get the actual value from the field_copy.string_value . Please also read Handling String. |
JKII_FIELD_TYPE_OBJECT | Represent the object type (both input and output). You can get the actual value from the field_copy.string_value in string, like {"key2-1":123,"key2-2":[false,true,false]} . Please also read Handling String. |
JKII_FIELD_TYPE_ARRAY | Represent the array type (for both input and output). You can get the actual value from the field_copy.string_value in string, like [false,true,false] . Please also read Handling String. |
Handling string
The jkii library provides two methods to handle the buffer for outputting the parsed result in a string. These methods apply when you are getting a string, object, and array as the parsed result.
Specify the buffer as an input parameter
In this method, your code prepares the buffer for storing the parsed result. The library will copy the parsed result into the buffer. The copied string will be NULL terminated.
To use this method, set the pointer to the buffer in the
field_copy.string
members of each element in the array. Also, set the buffer size (bytes) in thefile_copy_buffer_size
members. The buffer size should include the string terminator.Use the original JSON string
In this method, the library returns the pointers to the original JSON string to notify the parsed result. Since the original JSON string is shared, the extracted string is not NULL terminated. The number of bytes of the extracted is to be determined by the
start
andend
members of the field.To use this method, set the
field_copy.string
members of each element in the array to NULL.
Allocating the buffer for tokens
The library needs to have the buffer for analyzing JSON tokens when parsing the JSON string.
The size of the buffer required depends on the complexity of the JSON. The buffer size of one jkii_token_t
array element is required per one JSON token. Here, a JSON token means a JSON key and its value (i.e., a value, an object, a whole array, an element of an array, and so on).
For example, the JSON {"key1":"value", "key2":{"key3":2}}
has the following seven tokens:
- The whole JSON
key1
value
key2
{"key3":2}
key3
2
There are the following two ways to allocate the buffer for the tokens.
Specify the allocated buffer
This is the method used in our sample code.
We prepare an array of jkii_token_t
and set the pointer of this array and the number of array elements in jkii_resource_t
. We then pass it when we execute the jkii_parse
method.
Use a callback function to allocate the necessary buffer
If you cannot allocate an adequate buffer statically, you can allocate the necessary buffer dynamically with a callback function assigned in jkii_parse_with_allocator
.
typedef jkii_resource_t* (*JKII_CB_RESOURCE_ALLOC)(size_t required_size);
typedef void (*JKII_CB_RESOURCE_FREE)(jkii_resource_t* resource);
Here is an example of implementing the callback function.
static jkii_resource_t* cb_alloc(size_t required_size)
{
jkii_resource_t* res =
(jkii_resource_t*)malloc(sizeof(jkii_resource_t));
if (res == NULL) {
return NULL;
}
jkii_token_t *tokens =
(jkii_token_t*)malloc(sizeof(jkii_token_t) * required_size);
if (tokens == NULL) {
free(res);
return NULL;
}
res->tokens = tokens;
res->tokens_num = required_size;
return res;
}
static void cb_free(jkii_resource_t* resource) {
free(resource->tokens);
free(resource);
}
The jkii library will estimate the necessary number of tokens and execute this callback function. The callback function should multiple the necessary number of tokens (required_size
) with the size of the jkii_token_t
struct to determine the necessary buffer size and allocate the buffer accordingly. The callback function then store the pointer to the allocated buffer and the number of tokens in the jkii_resource_t
struct.
If we are to use the jkii_parse_with_allocator
method instead of the jkii_parse
in our sample code, we will change the code as follows:
/* Parse a JSON string. */
jkii_parse_err_t res = jkii_parse_with_allocator(
json_string,
strlen(json_string),
fields,
cb_alloc,
cb_free);
/* Check if parsing the string failed. */
if (result != JKII_ERR_OK) {
return;
}