5. 数组解析

5. 数组解析

章节目录

1. 解析总体流程

2. 类型分辨

3. 数值解析

4. 字符串解析

5. 数组解析

6. 对象解析

5.1 JSON数组结构

先来看看前面实现的数组结构

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;
};

可以看出, __json_array​的结构其实很简单, 就是一个链表, head就是头结点, 每个节点里面包含节点的数据域:json_value_t value​和节点的指针域struct list_head list​这个结构里面就包含两个指针.

外部调用:

    case '[':
    {
        ret = __parse_json_array(cursor, end, depth, &val->value.array);
        if (ret < 0)
        {
            return ret;
        }

        val->type = JSON_VALUE_ARRAY;
        break;
    }

目标很明确, 实现__parse_json_array​函数.

看看函数声明: static int __parse_json_array(const char *cursor, const char **end, int depth, json_array_t *array)

这里可以看到多了一个参数depth​, 由于array是复合类型, 每一个数组元素都可以为不同的类型, 为了控制数组的深度, 可以通过参数depth​来记录.

static int __parse_json_array(const char *cursor, const char **end, int depth, json_array_t *arr)
{
    int ret;

    if (depth == JSON_DEPTH_LIMIT)
    {
        return -3;
    }

    INIT_LIST_HEAD(&arr->head);

    ret = __parse_json_elements(cursor, end, depth + 1, arr);
    if (ret < 0)
    {
        __destroy_json_elements(arr);
        return ret;
    }

    arr->size = ret;
    return 0;
}

函数内部先判断了深度, 这里深度判断定义了一个宏, JSON_DEPTH_LIMIT​为1024, 如此嵌套解析超过1024就报错.

然后是使用list.h​中自带的宏初始化链表结构.

核心的就是解析数组的元素, 即__parse_json_elements​函数.

如果出现错误会调用__destroy_json_elements(arr)​函数, 释放内存.

5.2 解析数组元素

一个JSON数组中可以包含0至多个元素, 且可以嵌套, 每个元素使用逗号,​分隔, 末尾的逗号不合法.

例:

  • 合法:

    • [](空数组), [1, 2], [1, "hello", true], [1, 2, [3, 4, 5, "hello"], null]
  • 不合法:

    • [,], [1, 2, ]

现在来分析一下如何解析数组的元素.

解析JSON数组元素的核心就是理解数组的结构和链表的使用.

struct __json_array
{
	struct list_head head;
	int size;
};

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

我们分析__json_array​结构, size​顾名思义, 就是显示这个数组的元素个数. head​就是链表的头节点结构. 而__json_element​结构中包含一个数据域json_value_t​和链表结构list​, 可以把__json_element​结构看做是一个普通意义上的链表节点, 包含数据域value​和指针域list​, 而__json_array​结构中的head​就是头指针, 所以我们的解析就是遍历整个字符串, 然后构造一个头指针为**head**的双向循环链表.

直接上代码:

static int __parse_json_elements(const char *cursor, const char **end, int depth, json_array_t *arr)
{
    int ret;
    int cnt = 0;
    json_element_t *elem;

    while (isspace(*cursor))
    {
        cursor++;
    }

    if (*cursor == ']')
    {
        *end = cursor + 1;
        return 0;
    }

    while (1)
    {
        elem = (json_element_t *)malloc(sizeof(json_element_t));
        if (elem == NULL)
        {
            return -1;
        }

        ret = __parse_json_value(cursor, &cursor, depth, &elem->value);
        if (ret < 0)
        {
            free(elem);
            return ret;
        }

        list_add_tail(&elem->list, &arr->head);
        cnt++;

        while (isspace(*cursor))
        {
            cursor++;
        }

        if (*cursor == ',')
        {
            cursor++;
            while (isspace(*cursor))
            {
                cursor++;
            }
        }
        else if (*cursor == ']')
        {
            break;
        }
        else
        {
            return -2;
        }
    }

    *end = cursor + 1;
    return cnt;
}

现在看这个代码应该比较简单, 除了正常的跳过空格, 和解析结束判断(判断右中括号), 核心就是26 ~ 34行.

我们知道, JSON数组中的每个元素可以是任何JSON标准中的类型, 在代码中体现就是struct __json_value​.

这里初始化构造了一个元素elem​, 通过__parse_json_value​解析出了一个value​, 也就是获取到了这个元素的数据域, 接下来将这个元素插入以array​结构中的head​为头的链表就可以了.

list_add_tail(&elem->list, &arr->head);​这个语句就实现了这样一个功能, 也就是将elem​插入以arr->head​为头的链表的末尾.

简单说一下list_add_tail​函数, 这个函数是list.h内核链表实现的工具函数, 实现了将指定节点插入链表的末尾的功能.

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *node, struct list_head *head)
{
	__list_add(node, head->prev, head);
}

/*
 * Insert a new entry between two known consecutive entries. 
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *node, struct list_head *prev, struct list_head *next)
{
	next->prev = node;
	node->next = next;
	node->prev = prev;
	prev->next = node;
}

可以看到, list_add_tail​函数调用了内部静态函数__list_add​, 只要有基础的链表知识, 就可以看出, __list_add​函数就是将node​插入prev​和next​之间.

那么list_add_tail​为什么是插入链表的末尾呢? 我们知道, list.h的链表实现是一个双向循环链表, 也即第一个元素的前驱元素为末尾元素, 末尾元素的后驱是第一个元素, list_add_tail​调用__list_add​的参数为node​, head->prev​和head​, node​是待插入的元素, 没什么好说的, **head->prev**就是头元素的前驱, 前面说了头元素的前驱就是末尾元素, 所以, 这里就是将**node**插入了末尾元素和头元素之间, 也就是成了新的末尾元素. ​

这里只讲了我自己的理解, 具体的可以通过实际代码学习了解.

至此, 我们就完成了JSON数组的解析, 还有没有提到的__destroy_json_elements​, 实现了json_element_t​的内存释放, 这个后面集中讲.

参考:

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

[2] 深入理解Linux内核之链表 list.h 功能实现原理、接口说明及示例代码_内核链表接口_猪哥-嵌入式的博客-CSDN博客

[3] 一文多图搞懂数据结构的双链表!-腾讯云开发者社区-腾讯云 (tencent.com)

posted @ 2023-08-08 11:42  明天咪西什么  阅读(3)  评论(0编辑  收藏  举报