目录
json格式的数据应用十分广泛,作为嵌入式开发,用C/C++比较多,这里介绍一个开源处理json的库:cJSON,这个库用起来很简单,但是也有些地方需要注意。这里来记录下。
注意:不同版本的cJSON是有差别的,不过都大同小异,本文使用的cJSON版本是1.7.15。
1.cJSON源码获取
2.cJSON的使用
将上面所有文件下载下来后,这里只需要关注两个文件:cJSON.c和cJSON.h
下面就来看看这两个文件中的代码如何使用:
2.1 增
相关API:
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
举例:
#include <stdio.h>
#include "cJSON.h"
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
int main(void)
{
char jsonBuf[] = "{\"name\":\"lhsmd\", \"sex\":\"男\", \"fav\":{\"eat\":\"water\"}}";
cJSON *cJSONTest = cJSON_Parse(jsonBuf); /* 记得调用cJSON_Delete释放空间 */
cJSON *p = cJSON_GetObjectItem(cJSONTest, "name");
if(cJSON_IsString(p)) /* 判断key的value是否为string */
{
printf("%s\r\n", p->valuestring); /* 输出 */
}
cJSON *p1 = cJSON_AddStringToObject(cJSONTest, "add", "me");
if(p1 != NULL)
{
char *p2 = cJSON_Print(cJSONTest); /* 按照格式输出json cJSON_PrintUnformatted表示格式化输出输出是一长串 */
if(p2 != NULL)
{
printf("%s\r\n", p2);
free(p2);
p2 = NULL;
}
}
cJSON_Delete(cJSONTest);
return 0;
}
输出结果:
2.2 删
相关API:
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
举例:
#include <stdio.h>
#include "cJSON.h"
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
int main(void)
{
char jsonBuf[] = "{\"name\":\"lhsmd\", \"sex\":\"男\", \"fav\":{\"eat\":\"water\"}}";
cJSON *cJSONTest = cJSON_Parse(jsonBuf); /* 记得调用cJSON_Delete释放空间 */
cJSON *p = cJSON_GetObjectItem(cJSONTest, "name");
if(cJSON_IsString(p)) /* 判断key的value是否为string */
{
printf("%s\r\n", p->valuestring); /* 输出 */
}
cJSON_DeleteItemFromObject(cJSONTest, "name");
char *p3 = cJSON_Print(cJSONTest); /* 按照格式输出json cJSON_PrintUnformatted表示格式化输出输出是一长串 */
if(p3 != NULL)
{
printf("%s\r\n", p3);
free(p3);
p3 = NULL;
}
cJSON_Delete(cJSONTest);
return 0;
}
输出:
2.3 改
相关API:
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
举例:
#include <stdio.h>
#include "cJSON.h"
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
int main(void)
{
char jsonBuf[] = "{\"name\":\"lh\", \"sex\":\"男\", \"fav\":{\"eat\":\"water\"}}";
cJSON *cJSONTest = cJSON_Parse(jsonBuf); /* 记得调用cJSON_Delete释放空间 */
cJSON *p = cJSON_GetObjectItem(cJSONTest, "name");
if(cJSON_IsString(p)) /* 判断key的value是否为string */
{
printf("%s\r\n", p->valuestring); /* 输出 */
}
cJSON_ReplaceItemInObjectCaseSensitive(cJSONTest, "name", cJSON_CreateString("smd"));
char *p2 = cJSON_Print(cJSONTest); /* 按照格式输出json cJSON_PrintUnformatted表示格式化输出输出是一长串 */
if(p2 != NULL)
{
printf("%s\r\n", p2);
free(p2);
p2 = NULL;
}
cJSON *json = cJSON_CreateObject();
if(json != NULL)
{
if(cJSON_AddStringToObject(json, "eat" ,"food"))
{
if(cJSON_ReplaceItemInObjectCaseSensitive(cJSONTest, "fav", json))
{
p2 = cJSON_Print(cJSONTest); /* 按照格式输出json cJSON_PrintUnformatted表示格式化输出输出是一长串 */
if(p2 != NULL)
{
printf("%s\r\n", p2);
free(p2);
p2 = NULL;
}
}
}
}
cJSON_Delete(cJSONTest);
return 0;
}
输出:
2.4 查
查在使用中是使用最多的了,这里给一个实际用到的例子:
下面一段json数据表示近三天的天气情况,现在需要用C语言将其中的日期、日期对应的天气情况、最高低气温、风向以及湿度等信息提取出来,json数据如下:
{
"results": [
{
"location": {
"id": "WT029G15ETRJ",
"name": "长沙",
"country": "CN",
"path": "长沙,长沙,湖南,中国",
"timezone": "Asia/Shanghai",
"timezone_offset": "+08:00"
},
"daily": [
{
"date": "2022-08-28",
"text_day": "阴",
"code_day": "9",
"text_night": "阴",
"code_night": "9",
"high": "37",
"low": "25",
"rainfall": "0.00",
"precip": "0.00",
"wind_direction": "西南",
"wind_direction_degree": "222",
"wind_speed": "5.36",
"wind_scale": "1",
"humidity": "83"
},
{
"date": "2022-08-29",
"text_day": "阴",
"code_day": "9",
"text_night": "阴",
"code_night": "9",
"high": "37",
"low": "24",
"rainfall": "0.00",
"precip": "0.00",
"wind_direction": "西南",
"wind_direction_degree": "244",
"wind_speed": "7.96",
"wind_scale": "2",
"humidity": "86"
},
{
"date": "2022-08-30",
"text_day": "阴",
"code_day": "9",
"text_night": "阴",
"code_night": "9",
"high": "32",
"low": "21",
"rainfall": "0.00",
"precip": "0.00",
"wind_direction": "北",
"wind_direction_degree": "341",
"wind_speed": "18.72",
"wind_scale": "3",
"humidity": "75"
}
],
"last_update": "2022-08-28T08:00:00+08:00"
}
]
}
想要的数据信息在键“daily”下,显然,要获取“daily”的内容,需要一层一层“剥开”,这个相对简单,只需要“剥开”两层:“results”->“daily”。其C语言代码如下:
#include <stdio.h>
#include "cJSON.h"
#include <stdlib.h>
#include <string.h>
char* jsonFromFile(const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp)
{
return NULL;
}
fseek(fp, 0, SEEK_END);
long len = ftell(fp); /* 获取文件大小 */
printf("len = %d\r\n", len);
fseek(fp, 0, SEEK_SET); /* 将文件读写指针再次定位到文件内容开头 */
char* buf = (char *)malloc( sizeof(char)*len + 1); /* 申请空间,记得释放 */
memset(buf, 0, sizeof(buf));
fread(buf, sizeof(char), len, fp);
fclose(fp);
return buf;
}
int main()
{
char* jsondata = jsonFromFile("1.json");
cJSON*root=cJSON_Parse(jsondata);
free(jsondata);
if(root != NULL)
{
printf("[INFO] Transfer OK!\r\n");
cJSON *p = cJSON_GetObjectItem(root, "results"); //result下是一个数组,且只有一个元素
if(p != NULL)
{
cJSON *results_json = cJSON_GetArrayItem(p, 0); //第0个元素
if(results_json != NULL)
{
printf("[INFO] results_json cJSON_GetArrayItem ok\r\n");
cJSON *daily_json = cJSON_GetObjectItem(results_json, "daily"); //获取“daily”的值
if(daily_json != NULL)
{
for(int i = 0;i < cJSON_GetArraySize(daily_json); i++)
{
printf("日期时间:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "date")->valuestring);
printf("白天天气:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "text_day")->valuestring);
printf("夜间天气:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "text_night")->valuestring);
printf("最高气温:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "high")->valuestring);
printf("最低气温:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "low")->valuestring);
printf("风向:%s\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "wind_direction")->valuestring);
printf("湿度:%s\r\n\r\n", cJSON_GetObjectItem(cJSON_GetArrayItem(daily_json, i), "humidity")->valuestring);
}
}
}
}
cJSON_Delete(root);
}
}
3.使用注意事项
在使用cJSON的过程,需要注意堆空间的释放,具体需要关注的对象有:
1.cJSON_Print系列,需要使用free或者cJSON_free释放
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
2.cJSON_Create系列,使用cJSON_Delete释放
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
3.cJSON_Parse,使用cJSON_Delete释放
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
cJSON_Delete和cJSON_Free(free)的区别:
cJSON_Delete会释放整个cJSON对象,而free只是释放某个节点的空间,在使用时只需要注意:变量类型为char*的使用free释放空间,变量类型为cJSON*的,使用cJSON_Delete释放空间。
4.cJSON源码简要分析
在cJSON源码的cJSON.h中,有一个很重要的结构体:
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
这是一个双向循环链表,事实上,从代码分析,在给定json的数据中,每一个键值对都是一个链表里面的一个节点。json数据的增删改查都是通过这个双向链表实现的。
如果有时间,在介绍学习具体的源码。