目录
3.1 解析一个 JSON 对象,获取其中项目(实时天气数据解析)
3.2 解析一个 JSON 数组,获取其中项目(未来或历史天气数据解析)
1. cJSON源码的获取
学习前首先要获取cJSON源码:cJSON download | SourceForge.net
下载的 cJSON 文件拖到 Linux 下看一下目录(我就是只看了 cJSON.c 和 cJSON.h ,最后次发现还有测试代码,哭辽,在网上找的资料最后发现是在 cJSON 内的 test 文件内就有);
README:
typedef struct cJSON { //cJSON结构体
struct cJSON*next,*prev; /* 遍历数组或对象链的前向或后向链表指针*/
struct cJSON *child; /*数组或对象的孩子节点*/
int type; /* key的类型*/
char *valuestring; /*字符串值*/
int valueint; /* 整数值*/
double valuedouble; /* 浮点数值*/
char *string; /* key的名字*/
} cJSON;
- cJOSN结构体为一个双向链表,并可通过child指针访问下一层。
- type变量决定数据项类型(键的类型),数据项可以是字符串可以是整形,也可以是浮点型。如果是整形值的话可从valueint,如果是浮点型的话可从valuedouble取出,以此类推。
- string可理解为节点的名称,综合此处的第2点可理解为“键”的名称。
2. JSON 的认识
JSON 是一种规范的存储数据和表示数据的文本格式。也就是 JSON 是一种格式,我们称其为JSON格式数据。我们只要掌握其格式即可。
广泛用于客户端与服务端的数据交换。例如:
我们打开bilibil,随便打开一个视频(我看的最多的一个视频,哈哈哈哈)后,按F12打开开发工具如下图所示:
上图右边的JSON格式数据仍然不是很直观,我们把这个文件下载下来:
将该文件(文件名:main)下载下来(右键->另存为),使用Notepad++打开如下图所示:
这是压缩过的 JSON 串,看起来可读性极差,我们使用 Notepad++ 的插件将其格式化:
注:JSTool 插件需要自己安装。
格式化后的JSON格式数据,层次结构清晰而又简洁,如下图所示:
啊! 这个 JSON 串 1万行,这个太长了,我们找一个简单的JSON格式数据来分析一下。
从天气预报(含实时/未来5-7天/未来逐小时) - 数据接口 - NowAPI
复制一段JSON格式数据,如下代码片段。
注意:JSON 格式数据没有注释。 /**/ 注释是在JSON格式中是非法的。
{
"success": "1",
"result": {
"weaid": "1",
"days": "2022-03-16",
"week": "星期三",
"cityno": "beijing",
"citynm": "北京",
"cityid": "101010100",
"temperature": "2℃/2℃",
"temperature_curr": "6℃",
"humidity": "58%",
"aqi": "79",
"weather": "阴",
"weather_curr": "多云",
"weather_icon": "http://api.k780.com/upload/weather/d/1.gif",
"weather_icon1": "",
"wind": "南风",
"winp": "3级",
"temp_high": "2",
"temp_low": "2",
"temp_curr": "6",
"humi_high": "0",
"humi_low": "0",
"weatid": "2",
"weatid1": "",
"windid": "4",
"winpid": "3",
"weather_iconid": "1"
}
}
3. 实践
目标:将天气和温度的数据取出来。
我想得到日期(days)和温度(temperature)的值,我的做法是直接在(root)根节点中去寻找 他俩,在程序执行时出现段错误,调试发现 temp_value 为空(0x0)。原因是没有理解右边的 json 结构,最外层花括号内有两个对象,success 和 result 。我们要找的项目:日期和温度,是在 result 内,也就是在 result 的子节点(child)中。cJSON 使用的双向链表,是通过遍历去搜索项目的。所以我们的做法是先获取到 root 节点,再在root节点中找到 result 节点,最后在 result 节点的子节点(child)中去寻找日期和温度。
修改代码:
后进行编译,GDB 调试如下:
23 temp_value = cJSON_GetObjectItem(root, "success");
(gdb)
24 if(temp_value->type == cJSON_String)
(gdb)
26 temp_print = cJSON_Print("success");
(gdb)
27 printf("temperature: %s", temp_print);
(gdb)
30 days_value = cJSON_GetObjectItem(root, "days");
(gdb) p temp_value
$1 = (cJSON *) 0x607520
(gdb) p *temp_value
$2 = {next = 0x6075b0, prev = 0x0, child = 0x0, type = 4, valuestring = 0x607590 "1", valueint = 0, valuedouble = 0,
string = 0x607570 "success"}
(gdb) p *temp_value->next
$3 = {next = 0x0, prev = 0x607520, child = 0x607620, type = 6, valuestring = 0x0, valueint = 0, valuedouble = 0,
string = 0x607600 "result"}
(gdb) p *temp_value->next->child
$4 = {next = 0x6076b0, prev = 0x0, child = 0x0, type = 4, valuestring = 0x607690 "1", valueint = 0, valuedouble = 0,
string = 0x607670 "weaid"}
(gdb) p *temp_value->next->child->next
$5 = {next = 0x607740, prev = 0x607620, child = 0x0, type = 4, valuestring = 0x607720 "2022-03-16", valueint = 0,
valuedouble = 0, string = 0x607700 "days"}
(gdb)
修改后 temp_value 不为空了,可以看到 temp_value 指向对象 sucess; temp_value->next 指向 项目 result ;
查看 temp_value->next->child 指向项(item): weaid,可见子节点指向第一个项或对象;
查看 temp_value->next->child->next 指向项目 days。
3.1 解析一个 JSON 对象,获取其中项目(实时天气数据解析)
代码目录结构:
编译前先将 weather_realtime.json 在 JSON在线解析及格式化验证 - JSON.cn 去校验一下,以保证 JSON 格式没有问题。
编译:gcc -g cJSON.c weather_data.c -lm
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
/*解析一个json对象(object)中的项目(item)*/
static int parse_json_object(char *data)
{
cJSON *root = NULL;
cJSON *temp_item = NULL;
cJSON *days_item = NULL;
cJSON *result_object = NULL;
char *temp_print = NULL;
char *days_print = NULL;
root = cJSON_Parse(data); /*cJSON_Parse会申请一片内存,使用完后需要释放cJSON_Delete(root)掉*/
if(!root)
{
/*用于分析语法错误,返回一个指向解析错误的指针*/
printf("Eror before [%S]\n",cJSON_GetErrorPtr());
}
else
{
/*从 result 对象中获取项目"string"。不区分大小写。此处项目为 result*/
result_object = cJSON_GetObjectItem(root, "result");
temp_item = cJSON_GetObjectItem(result_object, "temperature");
if(temp_item->type == cJSON_String)
{
temp_print = cJSON_Print(temp_item); /*cJSON_Print会申请内存,使用完后需要释放(free掉)*/
printf("temperature: %s \n", temp_print);
free(temp_print);
}
days_item = cJSON_GetObjectItem(result_object, "days");
if(days_item->type == cJSON_String)
{
days_print = cJSON_Print(days_item);
printf("days: %s \n", days_print);
free(days_print);
}
}
cJSON_Delete(root);
return 0;
}
/*打开json文件,读取json格式数据*/
static int do_file (char *filename)
{
FILE *fp;
long len;
char *data;
fp = fopen(filename, "r");
fseek(fp, 0, SEEK_END); /*从文件末尾偏移0长度给fp文件指针*/
len = ftell(fp);
fseek(fp, 0, SEEK_SET); /*从文件起始偏移0长度给fp文件指针*/
data = (char*)malloc(len+1);
int ret = fread(data, 1, len, fp);
if(-1 == ret )
{
fclose(fp);
return ;
}
parse_json_object(data);
free(data);
fclose(fp);
return 0;
}
int main (int argc, const char *argv[])
{
char *filename;
filename = "weather_realtime.json";
do_file(filename);
return 0;
}
注意内存泄露:使用完需要释放
注:上图参考CJson开源库使用及注意事项_哔哩哔哩_bilibili
运行结果;
3.2 解析一个 JSON 数组,获取其中项目(未来或历史天气数据解析)
数组json:(未来天气数据json)
{
"success": "1",
"result": [{
"weaid": "1",
"days": "2022-03-17",
"week": "星期四",
"cityno": "beijing",
"citynm": "北京",
"cityid": "101010100",
"temperature": "3℃/-1℃",
"humidity": "0%/0%",
"weather": "雨夹雪转阴"
}, {
"weaid": "1",
"days": "2022-03-18",
"week": "星期五",
"cityno": "beijing",
"citynm": "北京",
"cityid": "101010100",
"temperature": "2℃/-2℃",
"humidity": "0%/0%",
"weather": "小雪转多云"
}, {
"weaid": "1",
"days": "2022-03-19",
"week": "星期六",
"cityno": "beijing",
"citynm": "北京",
"cityid": "101010100",
"temperature": "7℃/-1℃",
"humidity": "0%/0%",
"weather": "多云转晴"
}
]
}
目标:获取 result 数组的每个元素的 days 和 weather 项目。
对于 cJSON 结构体内的 next 指针和 child 指针的理解,当是并列关系是用的是 next ,是包含关系时用的是 child 指针。比如,未来天气数据json中,success 和 result 是两个对象,是并列关系;result 数组对象中又有 3 个对象,那么找到result 数组对象内的对象,需要使用 child 指针,child 指针指向 result 的第一个对象(首对象),此时要找下一个对象(第二个对象)时要用 next指针,如果要找首对象的第一个项目时要用 child 指针,找第二个项目时 child->next 指向第二个项目。以上的文字用 GDB 调试如下:
(gdb) p *result_object
$1 = {next = 0x0, prev = 0x6076a0, child = 0x6077a0, type = 5, valuestring = 0x0,
valueint = 0, valuedouble = 0, string = 0x607780 "result"}
(gdb) p *result_object->child result内的首对象,因为首对象的花括号外没有(key)键名,
所以该结构体内的 string = 0x0 (空)
$12 = {next = 0x607d00, prev = 0x0, child = 0x6077f0, type = 6, valuestring = 0x0,
valueint = 0, valuedouble = 0, string = 0x0}
(gdb) p *result_object->child->child->next result内的首对象的第二个项目
$11 = {next = 0x607910, prev = 0x6077f0, child = 0x0, type = 4,
valuestring = 0x6078f0 "2022-03-17", valueint = 0, valuedouble = 0,
string = 0x6078d0 "days"}
(gdb) p *result_object->child->next->child->next result内的第 2 个对象的第二个项目
$13 = {next = 0x607e70, prev = 0x607d50, child = 0x0, type = 4,
valuestring = 0x607e50 "2022-03-18", valueint = 0, valuedouble = 0,
string = 0x607e30 "days"}
(gdb) p *result_object->child->next->next->child->next result内的第 3 个对象的第二个项目
$14 = {next = 0x6083d0, prev = 0x6082b0, child = 0x0, type = 4,
valuestring = 0x6083b0 "2022-03-19", valueint = 0, valuedouble = 0,
string = 0x608390 "days"}
目录结构:
Makefile文件,这个Makefile 可以将目标文件编译出动态库,静态库,自带的测试程序编译成目标文件,参考:(18条消息) CJSON源码研究笔记_coding__madman的博客-CSDN博客_cjson源码
OBJ = cJSON.o
LIBNAME = libcjson
WEATHER_FUTURE = weather_future
PREFIX ?= /usr/local
INCLUDE_PATH ?= include/cjson
LIBRARY_PATH ?= lib
INSTALL_INCLUDE_PATH = $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
INSTALL_LIBRARY_PATH = $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
INSTALL ?= cp -a
R_CFLAGS = -fpic $(CFLAGS) -Wall -Werror -Wstrict-prototypes -Wwrite-strings -D_POSIX_C_SOURCE=200112L
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo false')
## shared lib
DYLIBNAME = $(LIBNAME).so
DYLIBCMD = $(CC) -shared -o $(DYLIBNAME)
## create dynamic (shared) library on Darwin (base OS for MacOSX and IOS)
ifeq (Darwin, $(uname_S))
DYLIBNAME = $(LIBNAME).dylib
## create dyanmic (shared) library on SunOS
else ifeq (SunOS, $(uname_S))
DYLIBCMD = $(CC) -G -o $(DYLIBNAME)
INSTALL = cp -r
endif
## static lib
STLIBNAME = $(LIBNAME).a
.PHONY: all clean install
all: $(DYLIBNAME) $(STLIBNAME) $(WEATHER_FUTURE)
$(DYLIBNAME): $(OBJ)
$(DYLIBCMD) $< $(LDFLAGS)
$(STLIBNAME): $(OBJ)
ar rcs $@ $<
$(OBJ): cJSON.c cJSON.h
.c.o:
$(CC) -ansi -pedantic -c $(R_CFLAGS) $<
$(WEATHER_FUTURE): cJSON.c cJSON.h weather_future.c
$(CC) -g cJSON.c weather_future.c -o weather_future -lm -I.
install: $(DYLIBNAME) $(STLIBNAME)
mkdir -p $(INSTALL_LIBRARY_PATH) $(INSTALL_INCLUDE_PATH)
$(INSTALL) cJSON.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
uninstall:
rm -rf $(INSTALL_LIBRARY_PATH)/$(DYLIBNAME)
rm -rf $(INSTALL_LIBRARY_PATH)/$(STLIBNAME)
rm -rf $(INSTALL_INCLUDE_PATH)/cJSON.h
clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(WEATHER_FUTURE) *.o
weather_future.c 源代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
/*解析一个json对象(object)中的项目(item)*/
static int parse_json_object(char *data)
{
cJSON *root = NULL;
cJSON *temp_item = NULL;
cJSON *days_item = NULL;
cJSON *result_object = NULL;
cJSON *arr_item = NULL;
char *temp_print = NULL;
char *days_print = NULL;
int i = 0;
int arr_size;
root = cJSON_Parse(data); /*cJSON_Parse会申请一片内存,使用完后需要释放cJSON_Delete(root)掉*/
if(!root)
{
/*用于分析语法错误,返回一个指向解析错误的指针*/
printf("Eror before [%S] \n",cJSON_GetErrorPtr());
}
else
{
/*获取数组对象result*/
result_object = cJSON_GetObjectItem(root, "result");
/*获取数组对象的长度*/
arr_size = cJSON_GetArraySize(result_object);
/*方式1:根据下标获取对象*/
for(i = 0; i < arr_size; i++)
{
temp_item = cJSON_GetArrayItem(result_object, i);
days_item = cJSON_GetArrayItem(result_object, i);
/*根据KEY获取对应的值*/
temp_item = cJSON_GetObjectItem(temp_item, "temperature");
if(temp_item->type == cJSON_String)
{
temp_print = cJSON_Print(temp_item); /*cJSON_Print会申请内存,使用完后需要释放(free掉)*/
printf("%s: %s \n", temp_item->string, temp_print);
free(temp_print);
}
days_item = cJSON_GetObjectItem(days_item, "days");
if(days_item->type == cJSON_String)
{
days_print = cJSON_Print(days_item);
printf("%s: %s \n", days_item->string, days_print);
free(days_print);
}
}
/*方式1结束*/
/*方式2:遍历对象*/
#if 0
/*获取数组对象的孩子节点(即第一个对象)*/
arr_item = result_object->child;
for(i = 0; i < arr_size; i++)
{
temp_item = cJSON_GetObjectItem(arr_item, "temperature");
if(temp_item->type == cJSON_String)
{
temp_print = cJSON_Print(temp_item); /*cJSON_Print会申请内存,使用完后需要释放(free掉)*/
printf("temperature: %s \n", temp_print);
free(temp_print);
}
days_item = cJSON_GetObjectItem(arr_item, "days");
if(days_item->type == cJSON_String)
{
days_print = cJSON_Print(days_item);
printf("days: %s \n", days_print);
free(days_print);
}
arr_item = arr_item -> next; /*移动到数组内的下一个对象*/
}
/*方式2结束*/
#endif
}
cJSON_Delete(root);
return 0;
}
/*打开json文件,读取json格式数据*/
static int do_file (char *filename)
{
FILE *fp;
long len;
char *data;
fp = fopen(filename, "r");
fseek(fp, 0, SEEK_END); /*从文件末尾偏移0长度给fp文件指针*/
len = ftell(fp);
fseek(fp, 0, SEEK_SET); /*从文件起始偏移0长度给fp文件指针*/
data = (char*)malloc(len+1);
int ret = fread(data, 1, len, fp);
if(-1 == ret )
{
fclose(fp);
return ;
}
parse_json_object(data);
free(data);
fclose(fp);
return 0;
}
int main (int argc, const char *argv[])
{
char *filename;
filename = "weather_future.json";
do_file(filename);
return 0;
}
编译: make
运行结果:
4. 遇到的问题:
问题1:
遇到一个问题1,在 Window 下的 Notepad++ 软件中创建一个 weather_realtime.json 文件,中文可以正确显示,但是把该文件拖入到 Linux 中后中文显示乱码。
于是查找资料,Linux中文显示乱码问题解决方法(编码查看及转换) - 整合侠 - 博客园 (cnblogs.com)
Linux系统与windows系统在编码上有显著的差别。Windows中的文件的格式默认是GBK(gb2312),而Linux系统中文件的格式默认是UTF-8。这两个系统就好比是中国和日本。文件就好比是一个人,如果要在另外的国家居住就要办理居住许可证,使用他国的证件(编码和字符集),否则是不被允许的黑户。因此,解决中文乱码问题要从编码和字符集着手。
解决方法是在创建 json 文件时,将编码方式选择为 UTF-8 然后保存为 json 格式文件。
问题2:
使用 gcc 编译,无 error 错误,无法生成可执行程序。
tmp/ccGjWBtT.o: In function `parse_number':
/home/chen.jianshen/workspace/cJSON_demo/cJSON.c:109: undefined reference to `pow'
/tmp/ccGjWBtT.o: In function `print_number':
/home/chen.jianshen/workspace/cJSON_demo/cJSON.c:169: undefined reference to `floor'
原因:使用了#include<math.h> , 忘记了链接动态库 加一个-lm 即可
问题3:
查资料时看到的暂记录。
使用时发现,在json字符串后面加字符例如:{"name":"123"}2a, cJSON_parse解析正确,能正常获取字段。但是如果在前面加干扰字符就会出问题,例如a{"name":"123"},cJOSN_parse依旧返回的不是空指针,但是并不能正确获取字段,需要在cJSON_GetObjectItem时再判断一次指针是否为空!
————————————————
版权声明:本文为CSDN博主「lemon1995」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013578795/article/details/87856115
5. 参考资料
2. 以视频的方式对 cJSON 进行讲解CJson开源库使用及注意事项_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1z5411s7pk?from=search&seid=14320891041539751008&spm_id_from=333.337.0.0
5. 递归的方式打印键值对:(18条消息) cJSON 使用详解_无痕眼泪的博客-CSDN博客_cjsonhttps://blog.csdn.net/qq_32172673/article/details/88305781
*6. 对cJSON源码进行剖析
(18条消息) cJSON源码及解析流程详解_Tyler_Zx的博客-CSDN博客_cjson源码解析https://blog.csdn.net/qq_38289815/article/details/1033072627. json在线校验工具
JSON在线解析及格式化验证 - JSON.cnhttps://www.json.cn/*8. 讲的白话,比较通俗