Parsing JSON

The Thing-IF SDK provides the "kii_json" 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 kii_json library in this page. Please read the Kii JSON Documentation and 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 kii_json library.

In the first example, we will show how to get the value of the field key1 in an integer from the JSON {"key1" : 123}.

Please note that we assume that the KII_JSON_FIXED_TOKEN_NUM macro explained in initializing token region is being set (this is already done in our reference implementation).

const char json_string[] = "{\"key1\" : 123}";
kii_json_t kii_json;
kii_json_field_t fields[2];
kii_json_parse_result_t result;

/* Initialize the kii_json_t struct. */
memset(&kii_json, 0x00, sizeof(kii_json_t));

/* Initialize fields. */
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/key1";
fields[0].type = KII_JSON_FIELD_TYPE_INTEGER;
fields[1].path = NULL;

/* Parse the JSON string. */
result = kii_json_read_object(
    &kii_json,
    json_string,
    sizeof(json_string) / sizeof(json_string[0]),
    fields);

/* Check if parsing the string failed. */
if (result != KII_JSON_PARSE_SUCCESS) {
  return;
}

/* Check if parsing the field failed. */
if (fields[0].result != KII_JSON_FIELD_PARSE_SUCCESS) {
  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:

  • Initialize the kii_json_t struct. This struct will be initialized in the library, so you just need to zero clear it as in this sample code.
  • Specify how to parse the fields in the kii_json_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 kii_json_read_object function.
  • Get the parsed result stored in the field_copy member of the kii_json_field_t array.

When successful, the function returns KII_JSON_PARSE_SUCCESS. Also, the KII_JSON_FIELD_PARSE_SUCCESS will be recorded in the result member of the kii_json_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];
kii_json_t kii_json;
kii_json_field_t fields[3];
kii_json_parse_result_t result;

/* Initialize the kii_json_t struct. */
memset(&kii_json, 0x00, sizeof(kii_json_t));

/* Initialize fields. */
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/key1";
fields[0].type = KII_JSON_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 = KII_JSON_FIELD_TYPE_BOOLEAN;
fields[2].path = NULL;

/* Parse the JSON string. */
result = kii_json_read_object(
    &kii_json,
    json_string,
    sizeof(json_string) / sizeof(json_string[0]),
    fields);

/* Check if parsing the string failed. */
if (result != KII_JSON_PARSE_SUCCESS) {
  return;
}

/* Check if parsing either field failed. */
if (fields[0].result != KII_JSON_FIELD_PARSE_SUCCESS ||
    fields[1].result != KII_JSON_FIELD_PARSE_SUCCESS) {
  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:

  • Initialize the kii_json_t struct.
  • Specify how to parse fields in the kii_json_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 the JSON by executing the kii_json_read_object function.
  • Get the parsed results stored in the field_copy members of the kii_json_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 KII_JSON_PARSE_PARTIAL_SUCCESS. 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 kii_json_field_t struct. This section explains how you to set the instructions.

First, declare the kii_json_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 or 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. KII_JSON_FIELD_PARSE_SUCCESS is set if the parsing was successful. Please check the Reference Guide 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 kii_json_boolean_t. The value will be either KII_JSON_TRUE or KII_JSON_FALSE
filed_copy_
buffer_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 kii_json library.

In the type member, please 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 KII_JSON_FIELD_PARSE_TYPE_UNMATCHED 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., KII_JSON_FIELD_TYPE_OBJECT or KII_JSON_FIELD_TYPE_INTEGER) but the actual JSON field had a null.

If you specify KII_JSON_FIELD_TYPE_ANY in the type member, the actual data type extracted will be returned.

Data type Description
KII_JSON_FIELD_TYPE_ANY Accept any data type. This can be set as the input. The actual data type extracted (e.g., object, array, or others) will be set as the output.
KII_JSON_FIELD_TYPE_INTEGER Represent the int type (for both input and output). You can get the extracted value from the field_copy.int_value.
KII_JSON_FIELD_TYPE_LONG Represent the long type (for both input and output). You can get the extracted value from the field_copy.long_value.
KII_JSON_FIELD_TYPE_DOUBLE Represent the double type (for both input and output). You get the extracted value from the field_copy.double_value.
KII_JSON_FIELD_TYPE_BOOLEAN Represent the boolean type (for both input and output). You get the extracted value from the field_copy.boolean_value.
KII_JSON_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.
KII_JSON_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.
KII_JSON_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.
KII_JSON_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 kii_json 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 kii_json_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 buffer size as a compiler option

This is the default method.

You can allocate the necessary buffer on the stack by setting the macro KII_JSON_FIXED_TOKEN_NUM. Please specify the number of tokens in this macro. If your platform has an adequate stack size, you can use this method to allocate the necessary resources easily.

The default MAKEFILE sets the macro to 128, so the buffer size of sizeof (kii_json_token_t) * 128 bytes will be allocated on the stack. This number should be sufficient for most cases. If your trait definition is very complex, please consider increasing the number.

When you are setting this macro, you can leave the callback function to be passed to the init_kii_thing_if_with_onboarded_thing or init_kii_thing_if functions in the Thing-IF SDK initialization to NULL.

The whole program will share the macro setting, but you can leverage the library from multiple tasks and threads to parse the JSON.

Use a callback function to allocate the necessary buffer

If you cannot allocate an adequate buffer on the stack, you can use the callback function to allocate the necessary buffer on the heap.

To use this method, leave the KII_JSON_FIXED_TOKEN_NUM macro undefined and implement the callback function to allocate the buffer for parsing JSON.

The following shows an example of the callback function:

static int resource_cb(kii_json_resource_t* resource, size_t required_size)
{
  kii_json_token_t *tokens;

  /* Reallocate the buffer for a JSON string. */
  tokens = (kii_json_token_t*)realloc(resource->tokens,
          sizeof(kii_json_token_t) * required_size);

  /* If reallocation fails */
  if (tokens == NULL) {
    return 0;
  }

  /* Set the reallocated pointer and the number of tokens in the kii_json_resource_t struct. */
  resource->tokens = tokens;
  resource->tokens_num = required_size;
  return 1;
}

The kii_json library will calculate the necessary number of tokens and execute this callback function. The required number will be passed as the required_size parameter. This sample callback function is expanding the tokens member of the kii_json_resource_t struct to accommodate the request. The callback stores the pointer to the reallocated buffer and the number of tokens in the kii_json_resource_t struct.

The function returns 1 if the buffer allocation succeeds, otherwise returns 0. If the function returns 0, the parsing JSON itself becomes an error.

Please note that if you follow this implementation, you need to free the allocated buffer from the tokens member of the kii_json_resource_t struct when the JSON parsing is done like the following:

/* Parse a JSON string. */
result = kii_json_read_object(
    &kii_json,
    json_string,
    sizeof(json_string) / sizeof(json_string[0]),
    fields);

/* Free the buffer. */
free(kii_json.resource->tokens);