cJSON使用详细教程 | 一个轻量级C语言JSON解析器

article/2025/8/26 21:04:39

1. JSON与cJSON

JSON —— 轻量级的数据格式

JSON 全称 JavaScript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。

它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。

JSON语法规则

JSON对象是一个无序的"名称/值"键值对的集合:

  • 以"{“开始,以”}"结束,允许嵌套使用;
  • 每个名称和值成对出现,名称和值之间使用":"分隔;
  • 键值对之间用","分隔
  • 在这些字符前后允许存在无意义的空白符;

对于键值,可以有如下值:

  • 一个新的json对象
  • 数组:使用"[“和”]"表示
  • 数字:直接表示,可以是整数,也可以是浮点数
  • 字符串:使用引号"表示
  • 字面值:false、null、true中的一个(必须是小写)

示例如下:

{"name": "mculover666","age": 22,"weight": 55.5,"address":{"country": "China","zip-code": 111111},"skill": ["c", "Java", "Python"],"student": false
}

cJSON

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。

cJSON项目托管在Github上,仓库地址如下:

https://github.com/DaveGamble/cJSON

使用Git命令将其拉取到本地:

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

从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:

  • cJSON.h
  • cJSON.c

使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:

#include "cJSON.h"

2. cJSON数据结构和设计思想

cJSON的设计思想从其数据结构上就能反映出来。

cJSON使用cJSON结构体来表示一个JSON数据,定义在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;

cJSON的设计很巧妙。

首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示,其中用来存放值的成员列表如下:

  • String:用于表示该键值对的名称;
  • type:用于表示该键值对中值的类型;
  • valuestring:如果键值类型(type)是字符串,则将该指针指向键值;
  • valueint:如果键值类型(type)是整数,则将该指针指向键值;
  • valuedouble:如果键值类型(type)是浮点数,则将该指针指向键值;

其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:

  • next指针:指向下一个键值对
  • prev指针指向上一个键值对

最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:

在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。

3. JSON数据封装

封装方法

封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。

首先来讲述一下链表中的一些术语:

  • 头指针:指向链表头结点的指针;
  • 头结点:不存放有效数据,方便链表操作;
  • 首节点:第一个存放有效数据的节点;
  • 尾节点:最后一个存放有效数据的节点;

明白了这几个概念之后,我们开始讲述创建一段完整的JSON数据,即如何创建一条完整的链表。

  • ① 创建头指针:
 cJSON* cjson_test = NULL;
  • ② 创建头结点,并将头指针指向头结点:
cjson_test = cJSON_CreateObject();
  • ③ 尽情的向链表中添加节点:
cJSON_AddNullToObject(cJSON * const object, const char * const name);cJSON_AddTrueToObject(cJSON * const object, const char * const name);cJSON_AddFalseToObject(cJSON * const object, const char * const name);cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);cJSON_AddObjectToObject(cJSON * const object, const char * const name);cJSON_AddArrayToObject(cJSON * const object, const char * const name);

输出JSON数据

上面讲述,一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?

cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:

(char *) cJSON_Print(const cJSON *item);

使用的时候,只需要接收该函数返回的指针地址即可。

封装数据和打印数据示例

单纯的讲述方法还不够,下面用一个例子来说明,封装出开头给出的那段JSON数据:

#include <stdio.h>
#include "cJSON.h"int main(void)
{cJSON* cjson_test = NULL;cJSON* cjson_address = NULL;cJSON* cjson_skill = NULL;char* str = NULL;/* 创建一个JSON数据对象(链表头结点) */cjson_test = cJSON_CreateObject();/* 添加一条字符串类型的JSON数据(添加一个链表节点) */cJSON_AddStringToObject(cjson_test, "name", "mculover666");/* 添加一条整数类型的JSON数据(添加一个链表节点) */cJSON_AddNumberToObject(cjson_test, "age", 22);/* 添加一条浮点类型的JSON数据(添加一个链表节点) */cJSON_AddNumberToObject(cjson_test, "weight", 55.5);/* 添加一个嵌套的JSON数据(添加一个链表节点) */cjson_address = cJSON_CreateObject();cJSON_AddStringToObject(cjson_address, "country", "China");cJSON_AddNumberToObject(cjson_address, "zip-code", 111111);cJSON_AddItemToObject(cjson_test, "address", cjson_address);/* 添加一个数组类型的JSON数据(添加一个链表节点) */cjson_skill = cJSON_CreateArray();cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java" ));cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */cJSON_AddFalseToObject(cjson_test, "student");/* 打印JSON对象(整条链表)的所有数据 */str = cJSON_Print(cjson_test);printf("%s\n", str);return 0;
}

编译运行:

gcc cJSON.c example1.c -o example1.exe

实验结果如图:

实验结果

该JSON数据链表的关系如图:

JSON关系图

4. cJSON数据解析

解析方法

解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。

解析方法如下:

  • ① 创建链表头指针:
cJSON* cjson_test = NULL;
  • ② 解析整段JSON数据,并将链表头结点地址返回,赋值给头指针:

解析整段数据使用的API只有一个:

(cJSON *) cJSON_Parse(const char *value);
  • ③ 根据键值对的名称从链表中取出对应的值,返回该键值对(链表节点)的地址
(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
  • ④ 如果JSON数据的值是数组,使用下面的两个API提取数据:
(int) cJSON_GetArraySize(const cJSON *array);
(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);

解析示例

下面用一个例子来说明如何解析出开头给出的那段JSON数据:

#include <stdio.h>
#include "cJSON.h"char *message = 
"{                              \\"name\":\"mculover666\",   \\"age\": 22,                \\"weight\": 55.5,           \\"address\":                \{                       \\"country\": \"China\",\\"zip-code\": 111111\},                      \\"skill\": [\"c\", \"Java\", \"Python\"],\\"student\": false          \
}";int main(void)
{cJSON* cjson_test = NULL;cJSON* cjson_name = NULL;cJSON* cjson_age = NULL;cJSON* cjson_weight = NULL;cJSON* cjson_address = NULL;cJSON* cjson_address_country = NULL;cJSON* cjson_address_zipcode = NULL;cJSON* cjson_skill = NULL;cJSON* cjson_student = NULL;int    skill_array_size = 0, i = 0;cJSON* cjson_skill_item = NULL;/* 解析整段JSO数据 */cjson_test = cJSON_Parse(message);if(cjson_test == NULL){printf("parse fail.\n");return -1;}/* 依次根据名称提取JSON数据(键值对) */cjson_name = cJSON_GetObjectItem(cjson_test, "name");cjson_age = cJSON_GetObjectItem(cjson_test, "age");cjson_weight = cJSON_GetObjectItem(cjson_test, "weight");printf("name: %s\n", cjson_name->valuestring);printf("age:%d\n", cjson_age->valueint);printf("weight:%.1f\n", cjson_weight->valuedouble);/* 解析嵌套json数据 */cjson_address = cJSON_GetObjectItem(cjson_test, "address");cjson_address_country = cJSON_GetObjectItem(cjson_address, "country");cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code");printf("address-country:%s\naddress-zipcode:%d\n", cjson_address_country->valuestring, cjson_address_zipcode->valueint);/* 解析数组 */cjson_skill = cJSON_GetObjectItem(cjson_test, "skill");skill_array_size = cJSON_GetArraySize(cjson_skill);printf("skill:[");for(i = 0; i < skill_array_size; i++){cjson_skill_item = cJSON_GetArrayItem(cjson_skill, i);printf("%s,", cjson_skill_item->valuestring);}printf("\b]\n");/* 解析布尔型数据 */cjson_student = cJSON_GetObjectItem(cjson_test, "student");if(cjson_student->valueint == 0){printf("student: false\n");}else{printf("student:error\n");}return 0;
}

编译:

gcc cJSON.c example2.c -o example2.exe

运行结果如图:

运行结果

注意事项

在本示例中,因为我提前知道数据的类型,比如字符型或者浮点型,所以我直接使用指针指向对应的数据域提取,在实际使用时,如果提前不确定数据类型,应该先判断type的值,确定数据类型,再从对应的数据域中提取数据

5. cJSON使用过程中的内存问题

内存及时释放

cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:

(void) cJSON_Delete(cJSON *item);

注意:该函数删除一条JSON数据时,如果有嵌套,会连带删除。

内存钩子

cJSON在支持自定义malloc函数和free函数,方法如下:

  • ① 使用cJSON_Hooks来连接自定义malloc函数和free函数:
typedef struct cJSON_Hooks
{/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */void *(CJSON_CDECL *malloc_fn)(size_t sz);void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
  • ② 初始化钩子cJSON_Hooks
(void) cJSON_InitHooks(cJSON_Hooks* hooks);

http://chatgpt.dhexx.cn/article/Eu9egWjH.shtml

相关文章

OSPF报文与LSA

1. OSPF报文 OSPF报文 Hello 报文、 DD 报文、 LSR 报文、 LSU报文、LSAck 报文 OSPF 头部 OSPF 用 IP来封装协议报文&#xff0c;协议号89&#xff0c;5种OSPF的报文具有相同OSPF 头部。 OSPF 头部中关注的字段主要有&#xff1a; version&#xff1a; IPv4 OSPFv2 值为2&…

OSPF中的LSA

LSA LSA的基本信息 LSA --- 链路状态通告 --- ospf协议在不同网络环境下产生的鞋带不同信息的载体 LSDB --- 链路状态数据库 SPF --- 最短路径优先算法 Type --- LSA的类型&#xff0c;在OSPFV2版本中&#xff0c;需要掌握的LSA类型一共有六中。 LinkState ID --- 链路状态…

OSPF中LSA相关内容

OSPF的LSA LSA — 链路状态通告 — OSPF协议在不同的网络环境下携带和传递的信息 LSDB — 链路状态数据库 SPF ---- 最短路径优先算法 [Huawei]dis ospf lsdb — 查看lsa信息 LSA头部&#xff08;之后的每条lsa信息都要携带此头部&#xff09; LSA头部内容&#xff1a; 1…

OSPF的Router-LSA和Network-LSA

文章目录 Router-LSARouter-LSA描述P2P网络Router-LSA描述MA网络或NBMA网络 Network-LSANetwork-LSA描述MA网络或NBMA网络 OSPF区域内LSDBSPF计算过程SPF算法构建SPF树计算最优路由查看OSPF路由表 单区域OSPF配置实现查看OSPF邻居状态 Router-LSA Router-LSA描述P2P网络 <R…

OSPF 之 6类LSA详解

目录 1类LSA&#xff1a;router -LSA 2类LSA &#xff0c;network LSA &#xff0c;网络LSA 3类LSA &#xff1a;summary LSA 汇总LSA 5类LSA&#xff1a; 外部LSA 4类LSA&#xff1a; summary ASBR LSA 7类LSA &#xff1a; NSSA LSA 1类LSA&#xff1a;router -LSA …

六类LSA及其作用

LSA&#xff08;链路状态通告&#xff09; LSA的组成 TYPE&#xff1a;LSA的类型&#xff0c;在OSPFV2中&#xff0c;需要掌握的有六种 LINK-State ID&#xff1a;链路状态标识符&#xff0c;用来标记一条LSA信息&#xff0c;相当于是一条LSA的名字 AdvRouter&#xff1a;通告…

图解 OSPF :什么是 LSA ?

大家好&#xff0c;我是小弗。我们都知道了&#xff0c;运行链路状态路由协议的路由器是交换链路状态信息。所有路由器都会生成自己直连接口状态的链路信息&#xff0c;并通告出去。路由器把在网络中收到的链路状态信息存入 LSDB&#xff08;链路状态数据库&#xff09;&#x…

ospf几种lsa

ospf网络类型 1.点到点 点到点网段 2.广播网络 transit网段&#xff08;至少有两台路由器的广播型网段&#xff09; 3.NBMA transit网段 4.点到多点 ospf网段的类型(网段的类型只与网络的类型有关) 1.transit网段 2.stub网段 3.点到点网段 4.virtual 一类的lsa rout…

OSPF的6种LSA

目录 一、6种LSA的简单介绍 1.1 1类LSA 1.1.1 1类LSA基础 1.1.2 1类LSA的报文格式&#xff1a; 1.1.3 1类LSA的链路类型&#xff08;link type&#xff09; 1.2 2类LSA 1.2.1 2类LSA基础 1.2.2 2类LSA的报文格式 1.3 3类LSA 1.3.1 3类LSA基础 1.3.2 3类LSA的报文…

LSA潜在语义分析

【转自&#xff1a;https://blog.csdn.net/roger__wong/article/details/41175967】 原文地址&#xff1a;http://en.wikipedia.org/wiki/Latent_semantic_analysis 前言 浅层语义分析&#xff08;LSA&#xff09;是一种自然语言处理中用到的方法&#xff0c;其通过“矢量语…

OSPF的LSA

文章目录 ospf的lsaLSA的头部信息一类LSA:Router二类LSA:Network三类LSA:Sum-net五类LSA&#xff1a;External四类LSA&#xff1a;Sum-Asbr ospf的lsa ospf本质是通过lsa(链路状态通告)洪泛&#xff0c;将运行ospf域内的所有lsa存放到本地lsdb(链路状态数据库)&#xff0c;然后…

LSA类型详解

在OSPF中有6种常用的LSA类型&#xff0c;分别为&#xff1a; Router-LSA&#xff08;1类&#xff09;、 Network- LSA&#xff08;2类&#xff09;、 Summary- LSA&#xff08;3类&#xff09;、 ASBR-Summary- LSA&#xff08;4类&#xff09;、 AS-External- LSA&#xff08;…

OSPF 之 LSA限制

目录 特殊区域 1.stub 区域&#xff0c; 末节区域 2.完全的末节区域 3.NSSA区域&#xff1a;&#xff08;not so stub area&#xff09; 非完全末节区域 4.完全的非完全的末节区域 LSA汇总 1. 3类LSA汇总&#xff1a; 2. 5类LSA 汇总&#xff1a; 3. 7类LSA 汇总&…

LSA类型

类型 LSID 通告者AdvRouter 作用范围 携带信息 Type-1LSA Router 通告者RID 区域内所有运行ospf协议的路由器的RID 单区域内部 拓扑信息&#xff0c;本地接口直连拓扑 Type-2LSA network DR接口的ip地址 DR锁在的路由器的RID 单区域内部 单个MA网络拓扑信息的补充…

OSPF协议总结5(六种LSA)

LSA----链路状态通告--- OSPF协议在不同网络环境下产生的携带不同信息的载体。 LSDB --链路状态数据库 SPF ---最短路径优先算法 查看LSDB数据库&#xff1a; Type --- LSA的类型&#xff0c;在OSPFV2版本中&#xff0c;需要掌握的L SA类型一共有6种。LinkState ID ---链路状态…

LSA笔记

http://www.360doc.com/content/22/0220/08/476286_1018188488.shtml 笔记 clear ip ospf process //慎用 &#xff0c;使用后会造成网络中断&#xff0c; 100&#xff0c;000&#xff0c;000bit //8次方比特&#xff0c;就是百兆速度&#xff0c; cost100M/接口带宽 hello&am…

LSA

Type-7 LSA : NSSA External LSA NSSA&#xff08;非完全末梢区域Not-So-Stubby Area)我们可以理解为从Stub Area衍生而来&#xff0c;StubArea是不允许外部路由进入的&#xff0c;而NSSA可以。当NSSA的ASBR向该区域注入外部路由时&#xff0c;这些外部路由将使用Type-7 LSA来描…

LSA(Latent semantic analysis)

LSA最初是用在语义检索上&#xff0c;为了解决一词多义和一义多词的问题&#xff1a; 1.一词多义&#xff1a; 美女和PPMM表示相同的含义&#xff0c;但是单纯依靠检索词“美女”来检索文档&#xff0c;很可能丧失掉那些包含“PPMM”的文档。 2.一义多词&#xff1a;如果输入检…

LSA详解

OSPF---1、2、3、4、5类LSA 描述一条LSA三要素&#xff1a;LSA类型、link-id链路标识符、ADV-router 产生者路由器 1类LSA: 功能&#xff1a;本路由器针对某个路由区域产生的路由器信息和部分拓扑信息 传输范围&#xff1a;本区域内部传输 link id&#xff1a;产生者路由器…

路由 OSPF LSA介绍、1~7类LSA详细介绍

1.0.0 路由 OSPF LSA介绍、1~7类LSA详细介绍 OSPF LSA 链路状态通告( Link status announcement)&#xff0c;作用于 向其它邻接OSPF路由器 传递拓扑信息与路由信息。 LSA如何去描述拓扑信息与路由信息的呢&#xff1f; 其实是基于不同类型LSA进行描述&#xff0c;而常见的LS…