Walkthrough

Description

The goal of this walk through is to complete a step by step example to better understand how to get started with Jsmn.

After completion, this task will parse station status JSON packets (shown below) and keep the latest information for each station. Task will have 2 variables for interfacing: a string to be populated with JSON packets and a command to parse packet.

{
    "station": 1,
    "status": "Completing", // "Starting", "Executing", or "Completing"
    "jobId": 115
}

Declare Variables and Types

First step is to delare all variables and types needed for the rest of our walkthrough. In most cases these would be declared in .var and .typ files, but for this example they are written in C. Attached completed task includes .var and .typ files for this example. Note: IEC type are commented next to c declarations.

    // Types
    UserData_typ {
        unsigned long station;  // (UDINT)
        unsigned long jobId;    // (UDINT)
        char[100] status;       // (STRING[99])
    }
    StationInfo_typ {
        unsigned long jobId;    // (UDINT)
        char[100] status;       // (STRING[99])
    }
    // Declare parser object
    jsmn_parser parser;

    // Declare tokens for temporary storage
    // The size of tokens array is dependent on the number of levels expected in json
    // This application requires a minium array size of 3
    jsmntok_t tokens[16]; 

    // Declare user storage to be used in callbacks
    UserData_typ myData;

    // Declare app storage for status info
    unsigned short NUM_STATIONS = 3; // (UINT)
    StationInfo_typ stationInfo[NUM_STATIONS];

    // Declare temporary variables
    signed short parseStatus; // (INT)

    // Interface variables
    unsigned short parsePacket; // (BOOL)
    char[501] json; // (STRING[500])

Initialize Variables

Next is to initialize the variables. Note: Variables declared in a .var file, by default will be initialized to zero. In this case those variables will not need to be set again. This code would typically be in the INIT routine but is not required to be.

    // Initialize to Zero
    brsmemset(&parser, 0, sizeof(parser));
    brsmemset(&tokens, 0, sizeof(tokens));
    brsmemset(&myData, 0, sizeof(myData));
    brsmemset(&stationInfo, 0, sizeof(stationInfo));
    parsePacket = 0;

    // Initialize other values to default
    JsmnInit(&parser);

    parser.callback.pFunction = &myCallback; // We will declare this function next
    parser.callback.pUserData = &myData;

    // Initialize json for testing
	brsstrcpy(json, "{ \
		\"station\": 1, \
		\"status\": \"Completing\", \
		\"jobId\": 115 \
		}");

Setup Callback

Next we need to declare a callback function. Callback (myCallback) function will need to handle the expected json packet shown in description, and populate myData with the values in the JSON string.

unsigned long myCallback(UserData_typ* data, jsmn_callback_data* callbackData) {
    if(callbackData->Size == 0) { // This check filters out callbackData that is not a primitive value

        if(brsstrcmp(callbackData->Structure[1], "station") == 0) { // Check if Key is "station"
            data->station = brsatoi(callbackData->Value); // Convert value from string to Integer
        }
        else if(brsstrcmp(callbackData->Structure[1], "status") == 0) { // Check if Key is "status"
            brsstrcpy(data->status, callbackData->Value);
        }
        else if(brsstrcmp(callbackData->Structure[1], "jobId") == 0) { // Check if Key is "jobId"
            data->jobId = brsatoi(callbackData->Value); // Convert value from string to Integer
        }
    }

    return 0; // No errors
}

Parse JSON

Finally we need to parse the JSON and handle the resulting information. To do this first we call JsmnParse with the configured parser. If function does not return an error, take myData and populate the cooresponding station with that information.

if(parsePacket) {
    // JSON packet populated in pJson
    parseStatus = JsmnParse((UDINT)&parser, json, brsstrlen(json), (UDINT)&tokens, sizeof(tokens)/sizeof(tokens[0]));

    if(parseStatus >= 0) { // Check that no errors occurred
        stationInfo[myData.station].jobId = myData.jobId;
        brsstrcpy(stationInfo[myData.station].status, myData.status);
    }

    // Re-Initialize parser object
    JsmnInit(&parser);

    // Reset command
	parsePacket = 0;
}

Callback Iterations

While parsing jsmnParse will call user defined callback (myCallback) several times each iteration with different data. Below shows what callbackData contains when passed into callback function. As you can see our callback ignores several callback iterations.

Iterations

Clean-up Variables

Before parsing the next packet variables will need to be cleaned up. In this example we only care about the parser object.

    // Re-Initialize parser object
    JsmnInit(&parser);

Complete

Task now will now support JSON station packets. To test task, open a watch window and add: json and parsePacket. Populate json values with desired test and set parsePacket to true. Additional error handling can be added including supporting partial packets and packets missing some attributes.

Task can be downloaded here.