开源项目学习:cJSON库的学习和使用(上)---以天气JSON数据为例

目录

1. cJSON源码的获取

2. JSON 的认识

3. 实践

3.1 解析一个 JSON 对象,获取其中项目(实时天气数据解析)

3.2 解析一个 JSON 数组,获取其中项目(未来或历史天气数据解析)

4. 遇到的问题:

问题1:

问题2:

问题3:

5. 参考资料


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. 参考资料

 1. 对 cJSON 头文件的函数进行详细中文说明: cJSON源码解析(超级详细!!!)_zhuikefeng的博客-CSDN博客_cjson源码解析https://blog.csdn.net/zhuikefeng/article/details/106975030?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5.pc_relevant_default&utm_relevant_index=8

2. 以视频的方式对 cJSON 进行讲解CJson开源库使用及注意事项_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1z5411s7pk?from=search&seid=14320891041539751008&spm_id_from=333.337.0.0

3. 可以简单快速掌握核心的 4 个函数(18条消息) 全面详解c语言使用cJSON解析JSON字符_17岁boy想当攻城狮的博客-CSDN博客_c语言json解析https://jrhar.blog.csdn.net/article/details/79173603?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2.pc_relevant_paycolumn_v3&utm_relevant_index=5

4. Makefile:(18条消息) CJSON源码研究笔记_coding__madman的博客-CSDN博客_cjson源码https://blog.csdn.net/coding__madman/article/details/51304093?spm=1001.2101.3001.6650.8&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-8.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-8.pc_relevant_default

 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. 讲的白话,比较通俗

(18条消息) cJSON的使用方法_rotation ㅤ   的博客-CSDN博客_cjson使用https://blog.csdn.net/fengxinlinux/article/details/53121287?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164752493316781683934721%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164752493316781683934721&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~top_positive~default-1-53121287.nonecase&utm_term=CJSON&spm=1018.2226.3001.4450

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值