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 the jkii_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 the jkii_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 the jkii_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 buffer buf in the input parameter field_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's key2-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.
  • Start parsing by executing the jkii_parse function.
  • Get the parsed result stored in the field_copy member of the jkii_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:
  • string: get the value as string and optionally specify the buffer. Please read Handling String for more details
  • int_value: get the value as int
  • long_value: get the value as long
  • double_value: get the value as double
  • boolean_value: get the value as boolean in jkii_boolean_t. The value will be either JKII_TRUE or JKII_FALSE
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 the file_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 and end 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;
}