二. JSON基础数据结构

二. JSON基础数据结构

专栏目录

一. JSON

二. JSON基础数据结构

三. JSON数据解析(parse)

0. 引

JSON是一个有着特殊结构的数据, 为了解析JSON, 需要使用编程语言将JSON的数据格式进行抽象, 有助于更好地, 快捷地实现JSON数据的解析.

为了使解析JSON结构的性能更好, 选用C语言实现JSON的数据结构的抽象, 以及底层的结构的解析功能实现.

1. JSON基础数据结构

JSON数据的类型只能为JSON标准所规定的五种类型:( JSON的基本数据类型)

  • 2. JSON的基本数据类型

    1. 数值: 十进制数, 不能有前导0, 可以为负数, 可以有小数部分. 还可以用e​或者E​表示指数部分. 不能包含非数, 如NaN. 不区分整数与浮点数.
    2. 字符串: 以双引号" "​括起来的零个或者多个​, 支持以反斜杠开始的转义字符序列.
    3. 布尔值: 表示为true​或者false
    4. 数组: 有序的零个或者多个值. 每个值可以为任意类型. 数组使用方括号包裹. 多个数组元素之间用逗号分隔.
    5. 对象: 若干无序的"键-值对", 其中键只能是字符串. 建议但不强制要求对象中的键是独一无二的. 对象以花括号{}​包裹. 多个键-值对之间使用逗号,​分隔. 键与值之间使用冒号:​分隔.
    6. 空值: 值写为null

1.1 JSON数据的抽象(json_value_t)

从最开始, 我们需要一个数据结构, 这个结构可以表示所有合法类型的JSON数据.

这个结构应该有以下成员:

  1. 数据的类型(type), 用于表示当前数据的类型, 由于json数据支持的类型只有6种, 所以这个成员使用int便可以存储
  2. 数据的值(value), 类型暂未知, 暂用TYPE​表示.
  • 根据以上的简单分析, 可以得到以下代码:

    typedef struct __json_value json_value_t
    
    struct __json_value
    {
        int type;
        TYPE value;
    };
    

    对于type, 所有的类型可能都已知, 其中bool​类型的true​和false​可作为单独的类型表示, 即总共存在7种类型, 可使用C语言int进行表示.

    对于value​, 最直觉的方式是可以使用一个结构体进行存储, 结构体中包含type​对应的value​, 而每一个特定的json​数据, 其type​和value​已经固定, 故可以使用union​进行表示.

    更近一步的, 对于null​类型和bool​类型中的true​和false​类型, 由于这三种类型的所对应的值只有一种固定的表示, 故可以直接使用type来表示其值.

    现在需要表示的value剩下: number​, string​, array​, object

    其中, number​​和string​​可直接使用C语言内置类型double​​和char *​​进行表示, array​​和object​​作为复合类型, 使用另一个自定义的结构体进行定义, 该结构体命名上为了和__json_value​​保持一致, 使用json_array_t​​和json_object_t​​表示, 其具体实现, 后面​展开​.

  • 综合上面的分析, 可以得到json数据的抽象结构如下

    #define JSON_VALUE_STRING	1
    #define JSON_VALUE_NUMBER	2
    #define JSON_VALUE_OBJECT	3
    #define JSON_VALUE_ARRAY	4
    #define JSON_VALUE_TRUE		5
    #define JSON_VALUE_FALSE	6
    #define JSON_VALUE_NULL		7
    
    typedef struct __json_value json_value_t;
    
    struct __json_value
    {
        int type;
        union
        {
            char *string;
            double number;
        	json_object_t object;
    	json_array_t array;
        } value;
    };
    

1.2 JSON数组结构抽象(json_array_t)

数组: 有序的零个或者多个值. 每个值可以为任意类型. 数组使用方括号包裹. 多个数组元素之间用逗号分隔.

从JSON标准对数组的定义, 可以得知, JSON数组本质上是一个有序的序列, 其他的描述, 如方括号包裹, 仅作为解析的标准, 不影响数据抽象.

为了存储一个有序的序列, 比较容易想到的方式就是数组或者链表, 下面简单分析一下数组和链表的优缺点.

  • 数组在内存中是连续分配的, 随机访问能力强(即访问第1个元素和访问第1000个元素的效率相同), 但也正因为数组是连续分配的, 所以数组扩容需要重新分配内存, 同时新增和修改元素的效率低.

  • 链表在内存中是不连续的, 新增和修改的效率高, 但随机访问能力低(访问都需要从头指针开始遍历).

    在JSON的array中, 随机访问的场景较少, 在解析中, 需要频繁插入元素, 同时插入的元素数量未知, 故在这里, 选用链表结构存储JSON数组会更好一些.

简单说一下链表的结构, 链表是由零个或多个节点组成的线性表. 每一个节点中大多包含一个数据域和一个指针域, 数据域存储当前节点的数据, 指针域指向下一个节点(双向链表指针域有两个, 一个指向前一个节点, 另一个指向后一个节点)

  1. 根据以上分析, 可以得到以下简单代码:

    typedef struct __json_array json_array_t;
    
    typedef struct __json_element json_element_t;
    
    struct __json_array
    {
        int size;
      
        json_element_t* head;
    };
    
    struct __json_element
    {
        json_value_t value;
        json_element_t* next, * prev;
    };
    

    上面代码是一个很简单的链表结构, __json_array​​结构包含两个值, 一个表示数组的大小, 另一个是一个类型为json_element_t​​的指针, 将**json_element_t**看做一个链表, 那么这个指针就是指向这个链表的头结点.

    将JSON的数组抽象为一个链表结构, 数组中的每个元素就可以抽象为链表里的一个个节点, 这样, JSON数组就可以通过链表进行抽象表示了.

    但仅仅是这样, 还是不能直接使用, 因为我们没有实现链表相关的操作, 需要自行实现链表的插入, 删除等操作.

实现链表的相关操作, 并不复杂, 在Barenboim/json-parser项目中, 使用的是Linux内核实现的链表, 为了致敬Linux内核源码, 和保持和Barenboim/json-parser的一致性, 所以也采用Linux内核源码(list.h)中所实现的链表结构.

Linux内核链表(list.h)

  • 这里简单说一下Linux内核实现的链表(list.h)

    struct list_head {
    	struct list_head *next, *prev;
    };
    
     #define LIST_HEAD_INIT(name)                                                  \
        {                                                                          \
            &(name), &(name)                                                       \
        }
    
    #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
    
    static inline void INIT_LIST_HEAD(struct list_head* list)
    {
        list->next = list;
        list->prev = list;
    }
    
    
  • 可以看到list_head​结构只有指针域, 而没有数据域, 这样设计是为了通用性, 想要真正使用这个链表, 需要将这个链表放在另一个自建的结构中组合使用.

    struct list_head {
        struct list_head *next, *prev;
    };
    
    struct __json_element
    {
        struct list_head list;
        json_value_t value;
    };
    
    • 经过改造, 可以看到, 我们将原本的__json_element​中的指针域换成内核链表struct __json_element​, 这样改造就完成了.

    • 改造成这个样子, 最大的问题是如何通过list指针获取到value, 原本的指针类型就是__json_element​, 可以直接通过->​操作符获取到value, 但是现在的难以获取到value的值.

    • 为了通过list指针获取到其宿主结构的其他成员, list.h​提供了一个list_entry​宏, 通过这个神奇的宏, 可以获取到list指针的宿主结构的指针.

      /**
       * list_entry - get the struct for this entry
       * @ptr:	the &struct list_head pointer.
       * @type:	the type of the struct this is embedded in.
       * @member:	the name of the list_struct within the struct.
       */
      #define list_entry(ptr, type, member)                                          \
          ((type*)((char*)(ptr) - (size_t)(&((type*)0)->member)))
      
      struct __json_element
      {
          struct list_head list;
          json_value_t value;
      };
      
      struct __json_element ele;
      struct __json_element eleNext = list_entry(&ele.list.next, struct __json_element, list);
      
      • 上面代码中举了一个简单的例子, 声明一个元素ele​, 那么通过list_entry​可以获取到指向ele​的下一个元素的指针.
      • 首先是拥有list的地址(即ptr)将其强转为char*是为了将这个指针的加减步长操作转为一字节, 这是减号的前半部分.
      • 减号后半部分, 设置一个type​类型的零指针, 在这里type为struct __json_element​, 获取这个0指针中的member​成员的地址, 由于这个结构体的地址为0, 那么通过0指针获取到的结构体中成员的地址, 就是相对于0的地址, 也就是这个成员在结构体中的地址偏移量.
      • 现在已知list的地址, 又已知​​**list**在其宿主结构体中的地址偏移量, 两个相减, 就得到了宿主结构体的地址​, 最后将其转为指定的type​就行了.

list.h中最关键的就是通过list_entry​​获取宿主结构体的指针, list.h中还包含其他链表相关的常规操作, 如添加节点, 删除节点等, 这里就不讲了.

  1. 经过链表的改造, 可以得到json数组的结构体如下:

    typedef struct __json_array json_array_t;
    
    typedef struct __json_element json_element_t;
    
    struct __json_array
    {
        int size;
      
        // json数组的头指针
        struct list_head head;
    };
    
    struct __json_element
    {
        struct list_head list;
        json_value_t value;
    };
    

1.3 JSON对象数据结构抽象

对象: 若干无序的"键-值对", 其中键只能是字符串. 建议但不强制要求对象中的键是独一无二的. 对象以花括号{}​包裹. 多个键-值对之间使用逗号,​分隔. 键与值之间使用冒号:​分隔.

根据上面JSON对象的标准定义, 我们可以知道, JSON对象类型, 是一个无序的序列, 序列中的每个成员是一个键值对.

JSON对象的抽象和JSON数组类似, 两者都是一个序列, 大体上都可以使用链表进行表示, 数组中是一个个的元素, 而对象中是一个个的键值对, 将一个个键值对称为对象的成员.

综合上面的分析, 可以初步得到如下代码:

typedef struct __json_object json_object_t;

struct __json_object
{
    int size;

    struct list_head head;
};

struct __json_member
{
    struct list_head list;
    char key[1];
    json_value_t value;
};
  • __json_object​结构中包含对象的size, 和指向对象的第一个成员的指针, 和数组结构一样, 其中包含一个链表结构.
  • __json_member​结构中包含链表指针, 和key, value, 很好理解

我们常说JSON, 常常是以一个JSON对象来说的, 经常遇到的一个场景就是需要去查找JSON对象中的一个键值对, 获取对应键中的值, 所以在JSON对象中做查找, 是很常用的一个功能.

众所周知, 链表查找的时间复杂度为O(n), 这在频繁查找的场景下, 效率是不够好的.

而说到查找, 常见的便于查找的数据结构就是二叉查找树, 如果我们在解析JSON结构的时候, JSON对象中的每个成员放入二叉查找树中, 那么未来查找就会比较方便了, 直接选用红黑树来助力实现JSON对象的查找.

我们可以直接引用linux内核的红黑树实现, 使用和链表的方式类似, 直接在我们原有的结构体中添加红黑树的指针就行了.

struct __json_object
{
    int size;

    struct list_head head;
    struct rb_root root;
};

struct __json_member
{
    struct list_head list;
    struct rb_node node;
    char key[1];
    json_value_t value;
};

__json_object​结构中添加了一个struct rb_root​结构, 这个表示红黑树的根节点, __json_member​结构中添加了红黑树的子节点结构rb_node

rbtree中包含了红黑树相关的操作函数, 这里不过多深究, 直接用就行了.

2. 总结

经过上面的分析, 我们已经把JSON所有的类型都进行了抽象, 总体的JSON的数据使用__json_value​进行表示, 不同的类型的值的表示方法总结如下

  1. 数值(number): ​直接使用double
  2. 字符串(string): 直接使用char *
  3. 布尔值(bool): 直接使用结构体中的type(int)的指定值进行表示
  4. 数组(array): 自定义一个__json_array_t​结构进行表示(存储结构为链表)
  5. 对象(object): 自定义一个__json_object_t​结构进行表示(存储结构为链表 + 红黑树)
#include "list.h"
#include "rbtree.h"

#define JSON_VALUE_STRING 1
#define JSON_VALUE_NUMBER 2
#define JSON_VALUE_OBJECT 3
#define JSON_VALUE_ARRAY 4
#define JSON_VALUE_TRUE 5
#define JSON_VALUE_FALSE 6
#define JSON_VALUE_NULL 7

typedef struct __json_value json_value_t;
typedef struct __json_object json_object_t;
typedef struct __json_array json_array_t;

typedef struct __json_member json_member_t;
typedef struct __json_element json_element_t;

struct __json_value
{
    int type;
    union
    {
        char *string;
        double number;
        json_object_t object;
        json_array_t array;
    } value;
};

struct __json_array
{
    int size;

    struct list_head head;
};

struct __json_element
{
    struct list_head list;
    json_value_t value;
};

struct __json_object
{
    int size;

    struct list_head head;
    struct rb_root root;
};

struct __json_member
{
    struct list_head list;
    struct rb_node node;
    char key[1];
    json_value_t value;
};

至此, 我们已经实现了JSON结构中的所有类型的C语言抽象, 接下来就是JSON数据的解析了, 如果对JSON结构有一定的了解, 解析不过是操作这个相应的数据结构罢了.

参考

[1] Workflow 源码解析 Json parser :part1 parse - 知乎 (zhihu.com)

[2] 维基百科(JSON)

posted @ 2023-07-16 23:29  明天咪西什么  阅读(48)  评论(0编辑  收藏  举报