C++ 进阶版:【CJsonObject】C++ JSON 解析器使用教程

1. JSON与cJSON

JSON —— 轻量级的数据格式

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

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

JSON语法规则

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

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

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

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

示例如下:

{
"name": "Koshkaaa",
"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数据链表的关系如图:

4. cJSON数据解析

解析方法

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

解析方法如下:

  • ① 创建链表头指针:
cJSON* cjson_test = NULL;
1
  • ② 解析整段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);

【cJSON】轻量级的C语言JSON解析器的更多相关文章

  1. 几百行代码实现一个 JSON 解析器

    前言 之前在写 gscript时我就在想有没有利用编译原理实现一个更实际工具?毕竟真写一个语言的难度不低,并且也很难真的应用起来. 一次无意间看到有人提起 JSON 解析器,这类工具充斥着我们的日常开 ...

  2. 如何编写一个JSON解析器

    编写一个JSON解析器实际上就是一个函数,它的输入是一个表示JSON的字符串,输出是结构化的对应到语言本身的数据结构. 和XML相比,JSON本身结构非常简单,并且仅有几种数据类型,以Java为例,对 ...

  3. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

  4. 高手教您编写简单的JSON解析器

    编写JSON解析器是熟悉解析技术的最简单方法之一.格式非常简单.它是递归定义的,所以与解析Brainfuck相比,你会遇到轻微的挑战 ; 你可能已经使用JSON.除了最后一点之外,解析 Scheme的 ...

  5. 面试题|手写JSON解析器

    这周的 Cassidoo 的每周简讯有这么一个面试题:: 写一个函数,这个函数接收一个正确的 JSON 字符串并将其转化为一个对象(或字典,映射等,这取决于你选择的语言).示例输入: fakePars ...

  6. 用c#自己实现一个简单的JSON解析器

    一.JSON格式介绍 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着很多优点.例如易读性更好,占用空间更 ...

  7. 手写Json解析器学习心得

    一. 介绍 一周前,老同学阿立给我转了一篇知乎回答,答主说检验一门语言是否掌握的标准是实现一个Json解析器,网易游戏过去的Python入门培训作业之一就是五天时间实现一个Json解析器. 知乎回答- ...

  8. 一起写一个JSON解析器

    [本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限 ...

  9. 这个东西,写C++插件的可以用到。 RapidJSON —— C++ 快速 JSON 解析器和生成器

    点这里 原文: RapidJSON —— C++ 快速 JSON 解析器和生成器 时间 2015-04-05 07:33:33  开源中国新闻原文  http://www.oschina.net/p/ ...

  10. [C语言]声明解析器cdecl修改版

    一.写在前面 K&R曾经在书中承认,"C语言声明的语法有时会带来严重的问题.".由于历史原因(BCPL语言只有唯一一个类型——二进制字),C语言声明的语法在各种合理的组合下 ...

随机推荐

  1. 总结(5)--- Numpy和Pandas库常用函数

    二.常用库 1.NumPy NumPy是高性能科学计算和数据分析的基础包.部分功能如下: ndarray, 具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组. 用于对整组数据进行快速运算的标准 ...

  2. 一次显著的性能提升,从8s到0.7s

    前言 最近我在公司优化了一些慢查询SQL,积累了一些SQL调优的实战经验. 我之前写过一些SQL优化相关的文章<聊聊SQL优化的15个小技巧>和<explain | 索引优化的这把绝 ...

  3. C语言源码的陷波器设计及调试总结

    一 前记 音频信号处理中,限波器是一个常用的算法.这个算法难度不是很高,可用起来却坑很多. 二 源码解析 1 滤波器的核心函数,这里注意两点,一个是带宽不能太宽了,太宽了杀伤力太大了,容易出问题.另外 ...

  4. python3使用pandas备份mysql数据表

    操作系统 :CentOS 7.6_x64 Python版本:3.9.12 MySQL版本:5.7.38 日常开发过程中,会遇到mysql数据表的备份需求,需要针对单独的数据表进行备份并定时清理数据. ...

  5. [ARC145B] AB Game

    The game is played by Alice and Bob. Initially, there are $n$ stones. The players alternate turns, m ...

  6. C# 字节数组、各进制字符串数据互转

    前言 日常开发过程中,格式转换是必不可少的重要环节,经常是十进制转十六进制.十六进制转byte数组又转换回来来回转换,最然进制转换很基础同时 C# 也提供了很多直接方便进行格式转换的方法,但是封装一个 ...

  7. 【Buildroot】工具包使用

    一.测试环境 开发板:rv1126 buildroot版本:2018.02-rc3 二..mk规则 PKG_NAME:定义软件包的名称.通常与软件包源代码的目录名相同. PKG_VERSION:定义软 ...

  8. C++ Qt开发:Tab与Tree组件实现分页菜单

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍tabWidg ...

  9. Excel对比两张表的某一列,匹配上则进行数据copy

    VLOOKUP(参数1,参数2,参数3,参数4) 参数1: 查找值 参数2:指定查找数据源的范围 参数3:返回查找区域的第几列数据 参数4:精确查找输入参数"0"or"f ...

  10. 14、Map

    1.Map的定义 map是Go中的内置类型,它将一个值与一个键关联起来.可以使用相应的键检索值.Map 是一种无序的键值对的集合.Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引 ...