首页 > 其他分享 >6. 对象解析

6. 对象解析

时间:2023-08-09 14:35:20浏览次数:37  
标签:__ member memb 对象 ret cursor json 解析

6. 对象解析

章节目录

1. 解析总体流程

2. 类型分辨

3. 数值解析

4. 字符串解析

5. 数组解析

6. 对象解析

6.1 JSON对象结构

JSON对象和数组类似, 是一个复合类型, 使用{ }​包括, 数组中每个元素都是一个JSON的value​, 而对象中的每一个成员是一个键值对, 以冒号':'分隔, 且键必须为字符串类型, 值可以为任意JSON类型, 每个成员由逗号分隔.

如: {'memb1":[1, 2, "hello"], "memb2" : "world"}

C语言实现这个结构如下:

typedef struct __json_object json_object_t

struct __json_object
{
    int size;

    struct list_head head;
    struct rb_root root;
};

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

可以看出, json_object_t​的结构和json_array_t​的结构类似, 就是多了一个struct rb_root root​, 这个是linux内核的红黑树实现, 前面说了, 为了提高JSON对象的查找效率, 所以使用了红黑树的结构, 这个不是我们的重点, 所以暂且知道怎么用就行了.

json_element_t​是一个和json_element_t​类似的结构, 内部包含一个value​即数据域表示键值对的值, 一个key​表示键值对的键名, 这里使用char key[1]只是做占位用, 在解析时分配内存. 其他的就是list​和数组元素结构里的list​相同, 然后多了一个红黑树的节点成员, 用于将当前的member​插入以json_object_t​结构中root​为根的红黑树.

外部调用:

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

        val->type = JSON_VALUE_OBJECT;
        break;
    }

模式和解析其他的类型相似, 看看解析object函数的实现:

static int __parse_json_object(const char *cursor, const char **end, int depth, json_object_t *obj)
{
    int ret;

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

    // 初始化链表和红黑树
    INIT_LIST_HEAD(&obj->head);
    obj->root.rb_node = NULL;

    ret = __parse_json_members(cursor, end, depth + 1, obj);
    if (ret < 0)
    {
        __destroy_json_members(obj);
        return ret;
    }

    obj->size = ret;
    return 0;
}

可以看出, 除了多初始化一个红黑树, 其他的和数组的类似, 其中核心就是__parse_json_member​函数.

6.2 解析对象中的成员

实现__parse_json_member​函数其实和解析数组的元素的函数相似, 只是对象的成员多了一个字符串类型的键, 在解析value​之前将键解析出来就行了.

static int __parse_json_members(const char *cursor, const char **end, int depth, json_object_t *obj)
{
    int ret;
    int cnt = 0;
    json_member_t *memb;

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

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

    while (1)
    {
        if (*cursor != '\"')
        {
            return -2;
        }

        cursor++;
        ret = __json_string_length(cursor);
        if (ret < 0)
        {
            return ret;
        }

        memb = (json_member_t *)malloc(offsetof(json_member_t, name) + ret + 1);
        if (memb == NULL)
        {
            return -1;
        }

        ret = __parse_json_member(cursor, &cursor, depth, memb);
        if (ret < 0)
        {
            free(memb);
            return ret;
        }

        __insert_json_member(memb, obj->head.prev, obj);
        cnt++;

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

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

    *end = cursor + 1;
    return cnt;
}

static int __parse_json_member(const char *cursor, const char **end, int depth, json_member_t *memb)
{
    int ret;

    ret = __parse_json_string(cursor, &cursor, memb->name);
    if (ret < 0)
    {
        return ret;
    }

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

    if (*cursor != ':')
    {
        return -2;
    }

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

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

    *end = cursor;
    return 0;
}

可以看到, 除了多解析了一个string类型的键name​之外, 其他的大部分和数组的解析类似, 不过将单个成员的解析另立了一个函数, 没有太大区别.

在对象的单个成员解析完毕后, 进行了一个__insert_json_member​的操作, 这里就是将解析出来的memb​添加到对象的红黑树和链表之中.

static int __insert_json_member(json_member_t *memb, struct list_head *pos, json_object_t *obj)
{
    struct rb_node **p = &obj->root.rb_node;
    struct rb_node *parent = NULL;
    json_member_t *entry;

    while (*p)
    {
        parent = *p;
        entry = rb_entry(*p, json_member_t, node);
        if (strcmp(memb->name, entry->name) < 0)
        {
            p = &(*p)->rb_left;
        }
        else
        {
            p = &(*p)->rb_right;
        }
    }

    rb_link_node(&memb->node, parent, p);
    rb_insert_color(&memb->node, &obj->root);
    list_add(&memb->list, pos);
}

这里默认对红黑树有基础, 先从父节点开始遍历, 查找memb​适合插入的节点位置, 找到合适的位置后, 进行插入和自平衡操作即rb_link_node​和rb_insert_color​, 最后将memb​也关联到一个链表一样, 操作和数组插入相同, 不过这个是插入到了链表的头结点之后而非尾结点之后.

到这里JSON数据的所有类型解析都实现了, 最后看看destroy相关函数的实现.

6.3 资源释放

在JSON数组和对象的解析中, 如果解析失败, 需要将分配的内存合理释放, 由于数组和对象结构是嵌套结构, 所以需要实现销毁数组元素和对象的成员的函数.

static void __destroy_json_elements(json_array_t *arr)
{
    struct list_head *pos, *tmp;
    json_element_t *elem;

    list_for_each_safe(pos, tmp, &arr->head)
    {
        elem = list_entry(pos, json_element_t, list);
        __destroy_json_value(&elem->value);
        free(elem);
    }
}

static void __destroy_json_value(json_value_t *val)
{
    switch (val->type)
    {
    case JSON_VALUE_STRING:
        free(val->value.string);
        break;
    case JSON_VALUE_ARRAY:
        __destroy_json_elements(&val->value.array);
        break;
    case JSON_VALUE_OBJECT:
        __destroy_json_members(&val->value.object);
        break;
    }
}

static void __destroy_json_members(json_object_t *obj)
{
    struct list_head *pos, *tmp;
    json_member_t *memb;

    list_for_each_safe(pos, tmp, &obj->head)
    {
        memb = list_entry(pos, json_member_t, list);
        __destroy_json_value(&memb->value);
        free(memb);
    }
}

释放的函数其实很简单, 遍历对应的链表, 然后调用销毁value的函数, 销毁value的函数内部根据不同的类型执行销毁函数即可.

参考:

[1] workflow 源码解析 : 基础数据结构 rbtree - 知乎 (zhihu.com)

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

[3] 红黑树 - 维基百科,自由的百科全书 (wikipedia.org)

[4] 二叉树 - 维基百科,自由的百科全书 (wikipedia.org)

[5] 二叉树及其作用浅析-腾讯云开发者社区-腾讯云 (tencent.com)

标签:__,member,memb,对象,ret,cursor,json,解析
From: https://www.cnblogs.com/xlqblog/p/object-analysis-zseait.html

相关文章

  • 缓存面试解析:穿透、击穿、雪崩,一致性、分布式锁、Redis过期,海量数据查找
    为什么使用缓存在程序内部使用缓存,比如使用map等数据结构作为内部缓存,可以快速获取对象。通过将经常使用的数据存储在缓存中,可以减少对数据库的频繁访问,从而提高系统的响应速度和性能。缓存可以将数据保存在内存中,读取速度更快,能够大大缩短数据访问的时间,提升用户体验。在业......
  • 使用python解析nginx日志
    性能测试时,需使用生产环境各接口请求比例分配接口请求比,nginx统计脚本如下:importreimportpandasaspdimportxlwtobj=re.compile(r'(?P<ip>.*?)--\[(?P<time>.*?)\]"(?P<request>.*?)"(?P<request_time>.*?)(?P<status>.*?)(?P<by......
  • 云监控---grafana使用mysql数据源创建dashboard--全面解析
    grafana的dashboard简介经常被用作基础设施的时间序列数据和应用程序分析的可视化。Grafana主要特性:灵活丰富的图形化选项;可以混合多种风格;支持多个数据源;拥有丰富的插件扩展;支持用户权限管理。Grafana有着非常漂亮的图表和布局展示,功能齐全的度量仪表盘dashboard和图形编辑......
  • C++类和对象_多态
    虚函数被virtual修饰的成员函数被称为虚函数,虚函数的地址会被纳入类的虚函数表(virtualfunctiontable)。inline和virtual不会同时生效,用virtual修饰内联函数时,编译器会忽视函数的内联属性,此时函数不再是内联。虚函数一定不是内联函数。虚函数的重写子类继承父类,并有一个与父类......
  • soso地图api接口地理解析geocoder检索示例----并在信息框显示经纬度
    api官网:http://api.map.soso.com/doc_v2/example.html?sample-geocoding-simple#8map示例代码如下(保存为html打开可见效果):<!DOCTYPEhtml><html><head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/><title>......
  • java解析json
    {"status":0,"message":"ok","total":2,"results":[{"name":"蓝光COCO金沙","location":{"lat":30.68754......
  • 可迭代对象,迭代器对象,for循环本质
    可迭代对象#可迭代对象#数据对象有__iter__方法的都称为可迭代对象1.内置方法通过加点的方式可以调用的方法2.__iter__读作:双下iter对象3.不可迭代对象:int,float4.可迭代对象:str,list,dict,tuple,set,f.__iter__文件对象5.可迭代的含义"""迭代:每一次更新......
  • Unity 编辑器资源导入处理函数 OnPostprocessAudio :深入解析与实用案例
    Unity编辑器资源导入处理函数OnPostprocessAudio用法点击封面跳转下载页面简介在Unity中,我们可以使用编辑器资源导入处理函数(OnPostprocessAudio)来自定义处理音频资源的导入过程。这个函数是继承自AssetPostprocessor类的,通过重写这个函数,我们可以在音频资源导入完成后执......
  • Unity 编辑器资源导入处理函数 OnPostprocessAudio :深入解析与实用案例
    Unity编辑器资源导入处理函数OnPostprocessAudio用法点击封面跳转下载页面简介在Unity中,我们可以使用编辑器资源导入处理函数(OnPostprocessAudio)来自定义处理音频资源的导入过程。这个函数是继承自AssetPostprocessor类的,通过重写这个函数,我们可以在音频资源导入完成后执......
  • 事件对象
    事件对象介绍事件对象:当事件发生的时候,浏览器会创建一个事件对象,这个对象包含了当前事件发生时的所有信息事件对象是一个全局对象,在事件发生时,浏览器会创建一个事件对象,并把它作为实参传递给事件处理函数,事件处理函数通过事件对象,可以获取到事件发生时的相关信息,如鼠标位置......