Practical Knowledge of JSON Data in Embedded Systems

JSON (JavaScript Object Notation) is a lightweight data interchange format. JSON is widely used in internet-related development, and it is also quite common in embedded systems. Recently, I used it in a project, and I would like to share my experience.

An example of a simple JSON formatted data is as follows:

 {
    "name": "xxx",
    "num": xxx,
    "c_score": xxx
}

Here, we need to understand a concept: key-value pair. For example:

"name": "xxx"

Such a structure is a key-value pair.

As the sender, we need to combine useful data like xxx into JSON format to send to the receiver; as the receiver, we need to parse the useful data from this JSON data.

For simple JSON data, we can use some string manipulation library functions in C to package and parse it, but for slightly more complex JSON, it may not be so easy to handle.

At this time, we can use a third-party library – cJSON, which makes data packaging and parsing very convenient.

cJSON library repository address:

https://github.com/DaveGamble/cJSON.git

Or:

https://gitee.com/mirrors/cJSON.git

Next, I will share an example of using the cJSON library for data packaging and parsing.

1. Packaging and Parsing Example

1. Determine Protocol Data

In actual development, to use JSON data for communication, we must first determine what data both parties will exchange. If necessary, a protocol document should also be written. The protocol document includes the data to be transmitted, data types, and other information.

For example:

Practical Knowledge of JSON Data in Embedded Systems

2. Example of Packaging JSON Data

Input some student information from the console, combine it into a string formatted JSON data packet, and then output it to the console.

Operation Example:

Practical Knowledge of JSON Data in Embedded Systems

First, we download the cJSON source code from the repository, the folder contents are as follows:

Practical Knowledge of JSON Data in Embedded Systems

We only need to copy the cJSON.c and cJSON.h files to the root directory of our project to use them, for example:

Practical Knowledge of JSON Data in Embedded Systems

From cJSON.h, we can see that it provides us with many interfaces:

Practical Knowledge of JSON Data in Embedded Systems

In this example, we only need to focus on the following interfaces:

cJSON_CreateObject: Create a JSON object, enclosed in { }
cJSON_CreateString: Create a string
cJSON_CreateNumber: Create int type data
cJSON_AddItemToObject: Add to JSON object
cJSON_Print: Present in standard JSON format
cJSON_PrintUnformatted: Present in JSON format without spaces
cJSON_Delete: Delete JSON object to free memory

The packaging function we created is as follows:

Swipe left and right to view all code >>>

static char *StudentsData_Packet(pStudentDef _Stu)
{
    char *res_string = NULL;    // Return value
    cJSON *name = NULL;         // Name
    cJSON *num = NULL;          // Student number
    cJSON *c_score = NULL;      // C language score

    /* Create a JSON object, enclosed in { } */
    cJSON *obj = cJSON_CreateObject();
    if (obj == NULL)
    {
        goto end;
    }

    /* Create the key-value pair "name": "xxx" */
    name = cJSON_CreateString(_Stu->name);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "name", name);

    /* Create the key-value pair "num": 207 */
    num = cJSON_CreateNumber(_Stu->num);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "num", num);
    
    /* Create the key-value pair "c_score": 95 */
    c_score = cJSON_CreateNumber(_Stu->c_score);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "c_score", c_score); 

    res_string = cJSON_Print(obj);          // Present in JSON format 
    // res_string = cJSON_PrintUnformatted(obj);   // Present in unformatted JSON

    if (res_string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

/* Handle exceptions uniformly with Delete (free) */
end:
    cJSON_Delete(obj);
    return res_string;
}

For detailed explanations, see the comments. Let’s focus on cJSON_Print and cJSON_PrintUnformatted among these interfaces.

The difference between these two interfaces is whether the combined JSON data contains spaces. We can see the difference through online JSON-related websites:

https://www.sojson.com/json/json_online.html

The JSON data with spaces, when using cJSON_Print, looks like this:

Practical Knowledge of JSON Data in Embedded Systems

The JSON data without spaces, when using cJSON_PrintUnformatted, looks like this:

Practical Knowledge of JSON Data in Embedded Systems

If you want to output for viewing, using cJSON_Print is more convenient; if it’s for actual communication, then using cJSON_PrintUnformatted is better, as it reduces the communication load by removing spaces.

Complete Code:

Swipe left and right to view all code >>>

/*
    Author: ZhengN
    Public Account: Embedded Miscellaneous
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"

#define  STU_NAME_LEN  32

/* Student structure */
typedef struct _Student
{
    char name[STU_NAME_LEN];  // Name      
    int num;                  // Student number      
    int c_score;              // C language score
}StudentDef, *pStudentDef;

/* Internal function declaration */
static char *StudentsData_Packet(pStudentDef _Stu);

/********************************************************************************************************
** Function: main
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: 
********************************************************************************************************/
int main(void)
{
    char name[STU_NAME_LEN] = {0};
    int num = 0;
    int c_score = 0;
    StudentDef stu;
    int stu_count = 0;
    int i = 0;

    /* Total number of students */
    printf("Please input number of student: ");
    scanf("%d", &amp;stu_count);

    while (i++ < stu_count)
    {
        /* Name */
        printf("Please input name: ");
        scanf("%s", name);
        if (strlen(name) < STU_NAME_LEN)
        {
            strncpy((char*)&amp;stu.name, name, strlen(name)+1);
        }
        else
        {
            printf("The name is too long\n");
        }
        
        /* Student number */
        printf("Please input num (0~100): ");
        scanf("%d", &amp;num);
        stu.num = num;

        /* C language score */
        printf("Please input c_score (0~100): ");
        scanf("%d", &amp;c_score);
        stu.c_score = c_score;

        /* Output JSON formatted student data */
        printf("%s\n", StudentsData_Packet(&amp;stu));
    }

    return 0;
}

/********************************************************************************************************
** Function: StudentsData_Packet, Student JSON formatted data packaging
**------------------------------------------------------------------------------------------------------
** Parameters: _Stu: Data needed to package student json data
** Description: 
** Returns: JSON formatted string 
********************************************************************************************************/
static char *StudentsData_Packet(pStudentDef _Stu)
{
    char *res_string = NULL;    // Return value
    cJSON *name = NULL;         // Name
    cJSON *num = NULL;          // Student number
    cJSON *c_score = NULL;      // C language score

    /* Create a JSON object, enclosed in { } */
    cJSON *obj = cJSON_CreateObject();
    if (obj == NULL)
    {
        goto end;
    }

    /* Create the key-value pair "name": "xxx" */
    name = cJSON_CreateString(_Stu->name);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "name", name);

    /* Create the key-value pair "num": 207 */
    num = cJSON_CreateNumber(_Stu->num);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "num", num);
    
    /* Create the key-value pair "c_score": 95 */
    c_score = cJSON_CreateNumber(_Stu->c_score);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "c_score", c_score); 

    res_string = cJSON_Print(obj);          // Present in JSON format 
    // res_string = cJSON_PrintUnformatted(obj);   // Present in unformatted JSON

    if (res_string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

/* Handle exceptions uniformly with Delete (free) */
end:
    cJSON_Delete(obj);
    return res_string;
}

For detailed explanations, see the comments. Let’s focus on cJSON_Print and cJSON_PrintUnformatted among these interfaces.

The difference between these two interfaces is whether the combined JSON data contains spaces. We can see the difference through online JSON-related websites:

https://www.sojson.com/json/json_online.html

The JSON data with spaces, when using cJSON_Print, looks like this:

Practical Knowledge of JSON Data in Embedded Systems

The JSON data without spaces, when using cJSON_PrintUnformatted, looks like this:

Practical Knowledge of JSON Data in Embedded Systems

If you want to output for viewing, using cJSON_Print is more convenient; if it’s for actual communication, then using cJSON_PrintUnformatted is better, as it reduces the communication load by removing spaces.

Complete Code:

Swipe left and right to view all code >>>

/*
    Author: ZhengN
    Public Account: Embedded Miscellaneous
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include "cJSON.h"

#define  STU_NAME_LEN  32

/* Student structure */
typedef struct _Student
{
    char name[STU_NAME_LEN];  // Name      
    int num;                  // Student number      
    int c_score;              // C language score
}StudentDef, *pStudentDef;

/* Internal function declaration */
static StudentDef StudentData_Prepare(void);
static char *StudentsData_Packet(pStudentDef _Stu);
static void StudentData_Send(const char *_data);

/********************************************************************************************************
** Function: main
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: 
********************************************************************************************************/
int main(void)
{
    StudentDef stu = {0};
    char *stu_data = NULL;
    int stu_count = 0;
    int i = 0;

    /* Total number of students to register */
    printf("Please input number of student: ");
    scanf("%d", &amp;stu_count);

    while (i++ < stu_count)
    {
        /* Prepare data */
        stu = StudentData_Prepare();

        /* JSON formatted data packaging */
        stu_data = StudentsData_Packet(&amp;stu);

        /* Send data */
        StudentData_Send(stu_data);
    }

    return 0;
}

/********************************************************************************************************
** Function: StudentData_Prepare, Prepare data needed for packaging
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: The obtained data
********************************************************************************************************/
static StudentDef StudentData_Prepare(void)
{
    char name[STU_NAME_LEN] = {0};
    int num = 0;
    int c_score = 0;
    StudentDef stu;

    /* Name */
    printf("Please input name: ");
    scanf("%s", name);
    if (strlen(name) < STU_NAME_LEN)
    {
        strncpy((char*)&amp;stu.name, name, strlen(name)+1);
    }
    else
    {
        printf("The name is too long\n");
    }
    
    /* Student number */
    printf("Please input num (0~100): ");
    scanf("%d", &amp;num);
    stu.num = num;

    /* C language score */
    printf("Please input c_score (0~100): ");
    scanf("%d", &amp;c_score);
    stu.c_score = c_score;

    return stu;
}

/********************************************************************************************************
** Function: StudentsData_Packet, JSON formatted data packaging
**------------------------------------------------------------------------------------------------------
** Parameters: _Stu: Data needed to package student json data
** Description: 
** Returns: JSON formatted string 
********************************************************************************************************/
static char *StudentsData_Packet(pStudentDef _Stu)
{
    char *res_string = NULL;    // Return value
    cJSON *name = NULL;         // Name
    cJSON *num = NULL;          // Student number
    cJSON *c_score = NULL;      // C language score

    /* Create a JSON object, enclosed in { } */
    cJSON *obj = cJSON_CreateObject();
    if (obj == NULL)
    {
        goto end;
    }

    /* Create the key-value pair "name": "xxx" */
    name = cJSON_CreateString(_Stu->name);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "name", name);

    /* Create the key-value pair "num": 207 */
    num = cJSON_CreateNumber(_Stu->num);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "num", num);
    
    /* Create the key-value pair "c_score": 95 */
    c_score = cJSON_CreateNumber(_Stu->c_score);
    if (name == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(obj, "c_score", c_score); 

    res_string = cJSON_Print(obj);          // Present in JSON format 
    // res_string = cJSON_PrintUnformatted(obj);   // Present in unformatted JSON

    if (res_string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

/* Handle exceptions uniformly with Delete (free) */
end:
    cJSON_Delete(obj);
    return res_string;
}

/********************************************************************************************************
** Function: StudentData_Send, Send JSON formatted string data
**------------------------------------------------------------------------------------------------------
** Parameters: _data: Data to be sent
** Description: 
** Returns: 
********************************************************************************************************/
static void StudentData_Send(const char *_data)
{
    WSADATA wd;
    SOCKET ClientSock;
    SOCKADDR_IN  ServerSockAddr;

    printf("%s\n\n", _data);
 
    /* Initialize the DLL required for sock operations */
    WSAStartup(MAKEWORD(2,2),&amp;wd);  
 
    /* Request to the server */
    memset(&amp;ServerSockAddr, 0, sizeof(ServerSockAddr));  
    ServerSockAddr.sin_family = AF_INET;
    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ServerSockAddr.sin_port = htons(1314);
 
    /* Create client socket */
    if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
    {
        printf("socket error!\n");
        exit(EXIT_FAILURE);
    }
    if (-1 == connect(ClientSock, (SOCKADDR*)&amp;ServerSockAddr, sizeof(SOCKADDR)))
    {
        printf("connect error!\n");
        exit(EXIT_FAILURE);
    }

    /* Send data to server */
    send(ClientSock, _data, strlen(_data), 0);
    
    /* Close socket */
    closesocket(ClientSock);   
}

json_parse.c Complete Code:

Swipe left and right to view all code >>>

/*
    Author: ZhengN
    Public Account: Embedded Miscellaneous
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include "cJSON.h"

#define  STU_NAME_LEN  32

/* Student structure */
typedef struct _Student
{
    char name[STU_NAME_LEN];  // Name      
    int num;                  // Student number      
    int c_score;              // C language score
}StudentDef, *pStudentDef;

/* Internal function declaration */
static char *StudentsData_Recv(void);
static void StudentsData_Parse(pStudentDef _Stu, const char *_JsonStudnetData);
static void PrintParseResult(const pStudentDef _Stu);
static void SaveParseResult(const pStudentDef _Stu);

/* Internal global variables */
static FILE *stu_fp = NULL;

/********************************************************************************************************
** Function: main
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: 
********************************************************************************************************/
int main(void)
{
    StudentDef stu = {0};   
    char *recv_data;         

    while (1)
    {
        /* Receive data */
        recv_data = StudentsData_Recv();

        /* Parse */
        StudentsData_Parse(&amp;stu, (const char*)recv_data);  

        /* Print output parse result */
        PrintParseResult(&amp;stu);  

        /* Save data to file */
        SaveParseResult(&amp;stu);  

        /* Free memory */ 
        free(recv_data);   // Prevent memory leak
        recv_data = NULL;  // Prevent dangling pointer
    }

    return 0;
}

/********************************************************************************************************
** Function: StudentsData_Recv, Receive data
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: 
********************************************************************************************************/
static char *StudentsData_Recv(void)
{
    WSADATA wd;
    SOCKADDR_IN ServerSockAddr;
    int recv_len = 0;
    char *recv_buf = (char*)malloc(512);
    static SOCKET ServerSock, ClientSock;
    static SOCKADDR ClientAddr;
    static int addr_size = 0;
    static int run_count = 0;

    /* The following operations only need to be executed once */
    if (0 == run_count)
    {
       /* Initialize the DLL required for sock operations */
        WSAStartup(MAKEWORD(2,2),&amp;wd);  
        
        /* Create server socket */
        if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
        {
            printf("server socket error!\n");
            exit(EXIT_FAILURE);
        }
        
        /* Set server information */
        memset(&amp;ServerSockAddr, 0, sizeof(ServerSockAddr));  // Clear the ServerSockAddr structure
        ServerSockAddr.sin_family = AF_INET;       // Use IPv4 address
        ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Local IP address
        ServerSockAddr.sin_port = htons(1314);      // Port

        /* Bind socket */
        if (-1 == bind(ServerSock, (SOCKADDR*)&amp;ServerSockAddr, sizeof(SOCKADDR)))
        {
            printf("bind error!\n");
            exit(EXIT_FAILURE);
        }

        printf("bind ok!\n"); 
        /* Enter listening state */
        if (-1 == listen(ServerSock, 10))
        {
            printf("listen error!\n");
            exit(EXIT_FAILURE);
        }
        printf("listen ok!\n");

        addr_size = sizeof(SOCKADDR);
    }
        
    run_count++;

    /* Listen for client requests, the accept function returns a new socket, which is used for sending and receiving */
    if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&amp;ClientAddr, &amp;addr_size)))
    {
        printf("client socket error!\n");
        exit(EXIT_FAILURE);
    }

    /* Accept the returned data from the client */
    memset(recv_buf, 0, 512);
    recv_len = recv(ClientSock, recv_buf, 512, 0);
    printf("%s\n", recv_buf);
    
    /* Close client socket */
    closesocket(ClientSock);

    /* Return the obtained JSON data */
    return (char*)recv_buf;
}

/********************************************************************************************************
** Function: StudentsData_Parse, JSON formatted student data parsing
**------------------------------------------------------------------------------------------------------
** Parameters: _JsonStudnetData: JSON data   _Stu: Store the useful data parsed
** Description: 
** Returns: 
********************************************************************************************************/
static void StudentsData_Parse(pStudentDef _Stu, const char *_JsonStudnetData)
{
    cJSON *student_json = NULL;   // student_json operation object, can represent the content enclosed in { }
    cJSON *name = NULL;             
    cJSON *num = NULL;
    cJSON *c_score = NULL;
    
    /* Start parsing */
    student_json = cJSON_Parse(_JsonStudnetData);
    if (NULL == student_json)
    {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            fprintf(stderr, "Error before: %s\n", error_ptr);
        }
        goto end;
    }

    /* Parse to get the value of name */
    name = cJSON_GetObjectItemCaseSensitive(student_json, "name");
    if (cJSON_IsString(name) &amp;&amp; (name->valuestring != NULL))
    {
        memcpy(&amp;_Stu->name, name->valuestring, strlen(name->valuestring));
    }

    /* Parse to get the value of num */
    num = cJSON_GetObjectItemCaseSensitive(student_json, "num");
    if (cJSON_IsNumber(num))
    {
        _Stu->num = num->valueint;
    }

    /* Parse to get the value of c_score */
    c_score = cJSON_GetObjectItemCaseSensitive(student_json, "c_score");
    if (cJSON_IsNumber(c_score))
    {
        _Stu->c_score = c_score->valueint;
    }

end:
    cJSON_Delete(student_json);
}

/********************************************************************************************************
** Function: PrintParseResult, Print output parse result
**------------------------------------------------------------------------------------------------------
** Parameters: 
** Description: 
** Returns: 
********************************************************************************************************/
static void PrintParseResult(const pStudentDef _Stu)
{
    printf("name: %s, num: %d, c_score: %d\n", _Stu->name, _Stu->num, _Stu->c_score);
}

/********************************************************************************************************
** Function: SaveParseResult, Save parse result
**------------------------------------------------------------------------------------------------------
** Parameters: _Stu: Data to be saved
** Description: 
** Returns: 
********************************************************************************************************/
static void SaveParseResult(const pStudentDef _Stu)
{
    char write_buf[512] = {0};
    static int stu_count = 0;

    /* Open file in append mode */
    if((stu_fp = fopen("ParseResult.txt", "a+")) == NULL)
    {
       printf("Open file error!\n");
       return exit(EXIT_FAILURE);
    } 

    /* Write into file in specified format */
    snprintf(write_buf, 512, "name: %s, num: %d, c_score: %d\n", _Stu->name, _Stu->num, _Stu->c_score);
    size_t len = fwrite((char*)write_buf, 1, strlen(write_buf), stu_fp);

    /* Offset file position pointer */
    fseek(stu_fp, len * stu_count, SEEK_SET);
    stu_count++;

    /* Close file */
    fclose(stu_fp);
}

Compilation commands:

Swipe left and right to view all code >>>

gcc json_print.c cJSON.c -o json_print.exe -lwsocket32
gcc json_parse.c cJSON.c -o json_parse.exe -lwsocket32

The comprehensive demo adds socket-related code, and this note mainly introduces JSON data packaging and parsing. Regarding socket-related content, no further explanation will be given. Interested friends can read previous notes related to sockets:

[Socket Notes] Summary of TCP and UDP Communication

[Socket Application] Implementation of Weather Client Based on C Language

Using Quectel 4G Module Dial-Up + Socket to Obtain Weather Data

Practical Knowledge of JSON Data in Embedded Systems

END

Source: Embedded Miscellaneous
Copyright belongs to the original author. If there is any infringement, please contact for deletion.
Recommended Reading
Implementing State Machine in C Language (Practical Edition)
Code Style: µCOS vs FreeRTOS
Ali’s ChatGPT is here, the first online experience is here!

→ Follow to avoid getting lost ←

Leave a Comment