要做存储业务,我解析了一个项目的源码

golcm
发布于 2023-6-21 11:53
浏览
0收藏

最近在做存储相关的业务,更具体的来说是存储相关的研发,于是就上网查了一下相关的资料,思虑再三打算从最简单的 Json 数据交换格式开始研究。

JSON是独立于编程语言的数据交换格式,几乎所有与网络开发相关的语言都有JSON函数库,例如:C语言里的cJSON,Golang语言里的package json,Java语言里的jackson,Python语言中的pyson等。

1、JSON介绍

2、cJSON源码分析

3、如何设计“key-value”的数据结构

4、如何设计JSON节点操作函数

5、如何设计解析JSON字符串函数

6、如何设计JSON节点转JSON字符串

1、JSON介绍

可能有些还是比较还在读书的学弟学妹还不知道什么是JSON,有什么约定,这里就大致介绍一下,已经了解的可以直接跳过该部分。

要开发JSON编解码器,当然要了解一下什么是JSON

JSON基本数据格式采用的是“键值对”,即“key : value”形式,其中key只能是字符串,而value的数据类型有多种;多个“键值对”之间使用逗号​​,​​​分隔,键与值之间用冒号​​:​​分隔

JSON的value基本数据类型:

  1. 数值(number):十进制数,不能有前导0,可以为负数,可以有小数部分。不区分整数与浮点数
  2. 字符串(string):以双引号​​""​​括起来的零个或多个Unicode码位。支持反斜杠开始的转义字符序列
  3. 对象(object):由无序的“键值对”组成,使用花括号​​{}​​包裹
  4. 数组(array):有序的零个或者多个值,每个值可以为任意类型,使用方括号包裹,形如:​​[value, value]​
  5. true:布尔值true
  6. false:布尔值false
  7. null:空值

四个特定字符被认为是空白符:空格符(' ')、水平制表符('\t')、回车符('\r')、换行符('\n')

JSON字符串中,在元素前后允许存在无意义的空白符(会被忽略)

JSON字符串示例

{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "children": [
    "Catherine",
    "Thomas",
    "Trevor"
  ],
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "spouse": null
}

2、cJSON源码分析

这里选择cJSON做进一步了解和解析,因为C语言是编程语言之母,也是我的第一门语言。

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

cJSON是github上一个star数为8.8k的仓库,是很多C/C++从业者开始学习源码知识都会开始选择的一个项目。

本文将对cJSON源码进行一个介绍,碍于篇幅原因写不了太多东西,这里只写一些源码解析过程中的宏观介绍。即使是这样,这篇文章也有将近5900 字之多。

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

所以具体的实现细节可以参考附详细注释的源码仓库:https://github.com/MingWangSong/MycJSON

我们了解清楚JSON数据的约定后,即可边提设计需求边分析cJSON是如何实现,

JSON编解码工具包主要提供JSON序列化和反序列化的这个两大块功能。

其中,序列化是指将编程语言结构化数据转化为JSON字符串;反序列化则是指相反的操作。

表述形象一点,JSON序列化就是输出或者打印JSON字符串,JSON反序列化操作是为了解析JSON字符串。

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区


JSON数据整体就是一个对象类型的数据,可以假象成一个key缺省的根节点;将一个“key-value”视为JSON中的基本结构,整个JSON中处处为“key-value”(这句式有Linux味)。

因此,要想实现JSON序列化和反序列化,则需定义“key-value”的数据结构,该结构称之为JSON节点

3、如何设计“key-value”的数据结构

  1. “key-value”数据结构的设计需要考虑两个事情:单一“key-value”的数据表示多个“key-value”之间的关系表示
  2. 针对多个“key-value”之间的关系表示,cJSON的设计想法是通过链表来串联JSON节点,具体来讲,使用单链表实现JSON嵌套关系,使用双向链表实现JSON同级关系。
    因此,后续的所有JSON节点操作都很链表相关。下面举例绘图说明:

{
  "firstName": "John",
  "lastName": "Smith",
  "children": [
    "Catherine",
    "Thomas",
    "Trevor"
  ],
  "address": {
    "streetAddress": "2nd-Street",
    "city": "New York"
  }
}

cJSON处理上述JSON数据的结构示意图如下:

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

  1. 在上图第一列中,黄色块为JSON根节点,该节点key缺省,value对应JSON字符串最外围​​{}​​包裹的若干个JSON节点,这些节点为根节点的子节点,图中红色箭头代表指向子节点的指针;
    第二列代表根节点下的一级JSON子节点,同级节点构成双链表,图中黑色指针为双链表的前驱和后继指针。
    值得注意的是,cJSON处理数组对象时,是将数组中每个元素视为一个JSON节点存储,同样采用双链表的形式存储,数组类型的JSON节点的value是没有值的,而是通过指向子节点的指针来存储数组地址,因此对象类型节点和数组类型节点的后续很多操作都可复用,非常的妙!
  2. cJSON节点数据结构设计如下:

typedef struct cJSON {
    struct cJSON *next;  // 同级节点后继指针
    struct cJSON *prev;  // 同级节点前驱指针
    struct cJSON *child; // 子节点指针
    int type;            // “value”的类型,一共有9种类型
    char *valuestring;   // “value”类型为字符串对应的存值变量
    int valueint;        // “value”类型为整数对应的存值变量(写入valueint已弃用)
    double valuedouble;  // “value”类型为浮点数对应的存值变量
    char *string;        // 键值对中的“key”值
} cJSON;

其中,​​next,prev,child​​分别代表前驱节点、后继节点和子节点,用于处理JSON数据节点间的层级关系;type表示该JSON节点的value类型。特别注意,变量string代表着key(

  1. 这个命名让我很不爽)。

因为类型定义中区分了布尔值的true和false两种类型,所以布尔类型的JSON节点实际不需要其他变量存储value值。具体类型定义如下:

#define cJSON_Invalid (0)       // 非法类型
#define
#define
#define
#define
#define
#define
#define
#define cJSON_Raw (1 << 7)      // raw json
#define
#define

4、如何设计JSON节点操作函数

无论是JSON序列化还是反序列化,都无法避免针对JSON节点的操作,因此先了解cJSON中JSON节点操作设计

  1. 创建JSON节点

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

  1. 上图为cJSON中创建JSON节点的调用流程图。根据cJSON源码实现方式,创建JSON结点函数可大致分为三类:创建非数组类型JSON节点,创建数组类型JSON节点,创建JSON节点引用。由于同类函数实现逻辑相似。
    创建JSON节点拢共分三步:开辟空间,设置节点类型,设置节点value。
    注意,创建节点的时候不会设置key值,因此要新增节点的时候不会直接调用此类函数,而是提供了对应的Add函数,后文将会详细介绍。
  • 创建非数组类型JSON节点
    创建非数组类型节点时,先调用​​​cJSON_New_Item()​​​为cJSON节点申请空间并用memset进行初始化,然后再设置节点类型和value。如果是添加指定的节点到指定的对象或者数组,则需设置​​next​​​或​​child​​指针,源码如下:

cJSON * cJSON_CreateObjectReference(const cJSON *child){
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item != NULL) {
        item->type = cJSON_Object | cJSON_IsReference;
        item->child = (cJSON*)cast_away_const(child);
    }
    return item;
}
cJSON * cJSON_CreateArrayReference(const cJSON *child){
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item != NULL) {
        item->type = cJSON_Array | cJSON_IsReference;
        item->child = (cJSON*)cast_away_const(child);
    }
    return item;
}
  • 创建数组类型JSON节点
    创建数组类型节点时,先调用​​​cJSON_New_Item()​​为cJSON节点申请空间并用memset进行初始化,然后再设置节点类型,接着,需要根据数组数组构建子节点链表,子节点链表是双向链表,并且头结点前驱指针指向尾结点,以便尾插入的时候快速定位尾结点。

2.新增JSON节点

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

由于cJSON中采用链表结构存储节点,因此新增JSON节点涉及的是链表中的插入操作。因为在cJSON中,value中的数组元素也是采用JSON节点来存储的,所以在数组用添加元素也是一样的链表操作。

由上图可知,新增JSON节点的核心函数是​​add_item_to_array()​​,该方法已尾插法的形式插入新建的JSON节点,为了能快速找到尾结点,每次插入时会更新头结点的前驱指针指向尾结点,具体插入过程如下图所示。

cJSON还提供了​​cJSON_InsertItemInArray()​​,该函数实现了在数组任意位置插入元素。

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

3.替换JSON节点

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

替换JSON节点的实现核心函数是​​cJSON_ReplaceItemViaPointer​​,主要是双链表中元素的替换操作。

4.删除JSON节点

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

删除JSON节点的核心函数是​​cJSON_Delete​​​和​​cJSON_DetachItemViaPointer​​​,递归删除指定节点及其所有子节点,但是cJSON_IsReference类型的节点不会删除。
其中,​​​cJSON_DetachItemViaPointer​​​主要处理待删除节点的相关指针;​​cJSON_Delete​​主要用于释放删除后的节点存储空间。

5.查找JSON节点

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

查找JSON节点的核心函数是​​get_object_item​​,由于是链表存储结构,因此只能遍历查找。

5、如何设计解析JSON字符串函数

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

使用cJSON来解析JSON节点时,直接调用​​cJSON_Parse()​​​函数,上图展示该函数的调用关系,核心函数是​​parse_value​​​和​​parse_object​​​。其中,​​parse_value​​实现了分别解析不同的类型的value值,布尔类型和​​null​​​类型直接在本函数中处理,数字、字符串、数组、对象类型通过封装函数分别处理;​​parse_object​​​主要实现了针对JSON节点的解析,通过先借助​​parse_string​​解析key值,再通过​​parse_value​​解析value值。

特别地,因为cJSON中嵌套对象和数组的数据结构设计是一样的,因此​​parse_object​​​和​​parse_array​​​的实现几乎一样,区别主要在于​​parse_array​​没有key值需要解析。

6、如何设计JSON节点转JSON字符串函数

要做存储业务,我解析了一个项目的源码-鸿蒙开发者社区

对比解析JSON字符串函数的函数调用图可以发现,JSON节点转JSON字符串函数的设计逻辑类似,也包含两个核心函数​​print_value​​​和​​print_object​​,这里就不做更进一步的解析了。

希望今天的源码解析能对大家有帮助,以后会带来更多的源码级项目解析。


参考链接

  • DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C (github.com):https://github.com/DaveGamble/cJSON
  • JSON - 维基百科,自由的百科全书 (wikipedia.org):https://zh.wikipedia.org/zh-cn/JSON
  • JSON:https://www.json.org/json-zh.html
  • cJSON源码及解析流程详解_Tyler_Zx的博客-CSDN博客:https://blog.csdn.net/qq_38289815/article/details/103307262
  • 如何解析JSON数据及内存钩子的使用方法-腾讯云开发者社区-腾讯云 (tencent.com):https://cloud.tencent.com/developer/beta/article/1662821
  • cJSON更换默认的malloc函数 - chilkings - 博客园 (cnblogs.com):https://www.cnblogs.com/chilkings/p/15821140.html
  • cjson库的使用以及源码阅读 - cfzhang - 博客园 (cnblogs.com):https://www.cnblogs.com/cfzhang/p/99da02ab2f02520d458c415a5314f83d.html
  • C语言中.h和.c文件解析(很精彩)-腾讯云开发者社区-腾讯云 (tencent.com):https://cloud.tencent.com/developer/article/1690858
  • MingWangSong/MycJSON (github.com):https://github.com/MingWangSong/MycJSON


文章转载自公众号:拓跋阿秀

分类
标签
已于2023-6-21 11:53:00修改
收藏
回复
举报
回复
    相关推荐