cJSON项目解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: cJSON项目解析

首先简单了解一下JSON。它是一种轻量级的数据交换格式。以一定的格式存储数据。看一个简单的例子就明白了:

{
  "name": "gao",
  "age": 21,
  "friends": ["张三", "李四"],
  "more": {
    "city": "湖北",
    "country": "中国"
  }
}

[]表示数组,{}表示对象,对象中包含键值对,值可以是对象、数组、数字、字符串或者三个字面值(false、null、true)中的一个,也说明了可以数组和对象可以嵌套使用。关于JSON详细介绍可以查阅相关资料,下面着重介绍cJSON项目。

cJSON是国外大神用c语言写的非常简单的生成和解析JSON数据格式的工具。只包含cJSON.h和cJSON.c两个文件,用一个main函数去调用相应的方法即可。

介绍一个JSON数据(生成解析均以下例为例)

{
  "奔驰": {
    "factory":  "一汽大众",
    "last": 31,
    "price":83,
    "sell": 49,
    "sum":  80,
    "other":[123, 1, "hello world", 
          {
            "梅赛德斯":"心所向"
          }
        ],
    "color":{
      "颜色":["红","绿","黑"]
    }
  }
}

下面用代码的方式生成这样一个格式的数据,并打印在控制台上:

数据包含四组{}对象、两组数组。最外层是一个大的对象obj,创建对象:

cJSON* obj = cJSON_CreateObject();

对象中包含奔驰键值对,值对应的又是一个对象subObj,subObj对象中包含一些键值对,对象中添加键值对:

cJSON_AddItemToObject(subObj, "factory", cJSON_CreateString("一汽大众"));
...

other键对应的值是一个数组array1,数组中包含一些元素和对象subsub1,数组中添加元素和对象:

//创建数组
cJSON* array1 =  cJSON_CreateArray();
cJSON_AddItemToArray(array1, cJSON_CreateNumber(123));
...
cJSON_AddItemToArray(array1, subsub1);

color键对应的值是一个对象subsub2,对象中颜色键对应的值是一个数组array2,因此需要创建array2数组,将该数组添加到subsub2对象中,color键值对再添加到奔驰对象中,最后将奔驰键值对添加到最大的obj对象中。

代码如下:

int main()
{
  //创建对象
  cJSON* obj = cJSON_CreateObject();
  //创建子对象(奔驰)
  cJSON* subObj = cJSON_CreateObject();
  //向奔驰对象中添加key-value
  cJSON_AddItemToObject(subObj, "factory", cJSON_CreateString("一汽大众"));
  cJSON_AddItemToObject(subObj, "last", cJSON_CreateNumber(31));
  cJSON_AddItemToObject(subObj, "price", cJSON_CreateNumber(83));
  cJSON_AddItemToObject(subObj, "sell", cJSON_CreateNumber(49));
  cJSON_AddItemToObject(subObj, "sum", cJSON_CreateNumber(80));
  //创建json数组(other)
  cJSON* array1 = cJSON_CreateArray();
  //向other数组中添加元素
  cJSON_AddItemToArray(array1, cJSON_CreateNumber(123));
  cJSON_AddItemToArray(array1, cJSON_CreateNumber(1));
  cJSON_AddItemToArray(array1, cJSON_CreateString("hello world"));
  //创建other中的对象
  cJSON* subsub1 = cJSON_CreateObject();
  cJSON_AddItemToObject(subsub1, "梅赛德斯", cJSON_CreateString("心所向"));
  //向other数组中添加对象
  cJSON_AddItemToArray(array1, subsub1);
  //向奔驰对象中添加other
  cJSON_AddItemToObject(subObj, "other", array1);
  //创建color中的subsub2对象
  cJSON* subsub2 = cJSON_CreateObject();
  //创建color中颜色数组
  cJSON* array2 = cJSON_CreateArray();
  //向颜色数组array2添加元素
  cJSON_AddItemToArray(array2, cJSON_CreateString("红"));
  cJSON_AddItemToArray(array2, cJSON_CreateString("绿"));
  cJSON_AddItemToArray(array2, cJSON_CreateString("黑"));
  //添加颜色数组到subsub2对象中
  cJSON_AddItemToObject(subsub2, "颜色", array2);
  //向奔驰对象中添加color key-value
  cJSON_AddItemToObject(subObj, "color", subsub2);
  //向obj中添加奔驰key-value
  cJSON_AddItemToObject(obj, "奔驰", subObj);
  //打印数据
  printf("带格式输出:\n");
  char* data = cJSON_Print(obj);
  printf("%s\n", data);
  printf("不带格式输出:\n");
  char* data1 = cJSON_PrintUnformatted(obj);
  printf("%s\n", data1);
  return 0;
}

下面将字符串解析成带格式的输出,需要注意把字符串中的双引号转义

int main()
{
  char* value = "{\"奔驰\":{\"factory\":\"一汽大众\",\"last\":31,\"price\":83, \
    \"sell\":49,\"sum\":80,\"other\":[123,1,\"hello world\",{\"梅赛德斯\":   \
    \"心所向\"}],\"color\":{\"颜色\":[\"红\",\"绿\",\"黑\"]}}}";  
  cJSON* obj1 = cJSON_Parse(value);
  char* data1 = cJSON_Print(obj1);
  printf("%s", data1);
}

下面具体说一下程序是如何运行的。

程序是采用双向链表的数据结构

/* The cJSON structure: */
typedef struct cJSON {
  struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
  struct cJSON *child;    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
  int type;         /* The type of the item, as above. */
  char *valuestring;      /* The item's string, if type==cJSON_String */
  int valueint;       /* The item's number, if type==cJSON_Number */
  double valuedouble;     /* The item's number, if type==cJSON_Number */
  char *string;       /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

以下例为例看看数据在链表中究竟是如何存储的:

{
  "奔驰": {
    "factory":  "一汽大众",
    "last": 31,
    "price":83,
    "color":{
      "颜色":["红","绿","黑"]
    }
  }
}

链表以逗号分隔结点:

提供了查询数组大小、删除结点等一系列方法:

int    cJSON_GetArraySize(cJSON *array);
void   cJSON_DeleteItemFromArray(cJSON *array,int which);
void   cJSON_DeleteItemFromObject(cJSON *object,const char *string);
...

程序最重要的是对字符串的解析,下面看一下程序是如何解析变换的。

跟踪源代码可以发现:

首先调用cJSON *cJSON_Parse(const char *value)传入压缩的字符串,再调用cJSON *cJSON_ParseWithOpts(const char *value,...)在该函数内调用static const char *parse_value(cJSON *item,const char *value)

/* Parser core - when encountering text, process appropriately. */
static const char *parse_value(cJSON *item,const char *value)
{
  if (!value)           return 0; /* Fail on null. */
  if (!strncmp(value,"null",4)) { item->type=cJSON_NULL;  return value+4; }
  if (!strncmp(value,"false",5))  { item->type=cJSON_False; return value+5; }
  if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1;  return value+4; }
  if (*value=='\"')       { return parse_string(item,value); }
  if (*value=='-' || (*value>='0' && *value<='9'))  { return parse_number(item,value); }
  if (*value=='[')        { return parse_array(item,value); }
  if (*value=='{')        { return parse_object(item,value); }
  ep=value;return 0;  /* failure. */
}

判断传入的value值第一个符号是什么,再分别调用相应的解析函数,因此要去除value中开头的空格,防止识别错误,这里用到一个函数,去除字符串开头的空格。

//去除字符串开头的空格
static const char *skip(const char *in) 
{ 
  while (in && *in && (unsigned char)*in<=32) 
    in++; 
  return in;
}

这里认为ASCII为32之前的都是非法字符(空格的ASCII十进制为32),不得不赞叹代码的简洁啊。同时这也是学习源码的好处。

再回到parse_value函数中,这里JSON字符串是对象"{“开始,因此调用parse_object,在parse_object中通过parse_string函数解析键、parse_value解析值,遇到”,"说明不止一个键值对继续进行。对于进入parse_value如果值还为对象则继续调用parse_object如此递归循环直到结束。解析数组也是同样的道理。根据字符串建立双向链表,完成后返回链表的头结点。

static const char *parse_string(cJSON *item,const char *str);
static const char *parse_number(cJSON *item,const char *num);
static const char *parse_array(cJSON *item,const char *value);
static const char *parse_object(cJSON *item,const char *value);

打印有两种方式,按格式打印和没有格式打印

/* Render a cJSON item/entity/structure to text. */
char *cJSON_Print(cJSON *item)        {return print_value(item,0,1,0);}
char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0,0);}

区别就在于print_value中的第三个参数。

static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
{
        switch ((item->type)&255)
    {
      case cJSON_NULL:  out=cJSON_strdup("null"); break;
      case cJSON_False: out=cJSON_strdup("false");break;
      case cJSON_True:  out=cJSON_strdup("true"); break;
      case cJSON_Number:  out=print_number(item,0);break;
      case cJSON_String:  out=print_string(item,0);break;
      case cJSON_Array: out=print_array(item,depth,fmt,0);break;
      case cJSON_Object:  out=print_object(item,depth,fmt,0);break;
    }
}

事实上也就是根据该参数判断是否打印换行符和制表符,如果按格式打印,则输出相应的换行符和制表符,看起来更加直观。如果没有格式,则只是字符串。(效果见图一)

static char *print_number(cJSON *item,printbuffer *p);
static char *print_string(cJSON *item,printbuffer *p);
static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p);
static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p);

剩下的工作就是在相应的函数,处理字符串了。最终返回一个char*类型的字符串,打印输出即可。

Linux命令:

gdb调试中,调试带参数的程序

gdb --args ./filname arg1 arg2

相关文章
|
4月前
|
监控 数据挖掘 BI
探索项目管理系统:解析五大功能,洞悉项目成功的关键
项目新手常忽视管理系统的价值,而高手已借助系统实现规划清晰。优秀的项目管理系统必备五大功能:项目WBS分解、图表报表、工时管理、团队协作和任务自动化。WBS能将复杂项目拆分成可管理任务,明确责任,评估时间和资源需求,便于跟踪进度。Zoho Projects作为示例,支持创建任务层级,利用甘特图和资源利用图监控进度和资源分配,工时管理则帮助控制项目时间和成本。同时,系统促进团队协作,如通过即时通讯和知识库增强团队凝聚力,而任务自动化则减少错误,提升效率。
81 1
|
4月前
|
Shell iOS开发 MacOS
|
4月前
|
资源调度 前端开发 JavaScript
构建高效前端项目:现代包管理器与模块化的深度解析
【2月更文挑战第21天】 在当今快速演变的前端开发领域,高效的项目管理和代码组织已成为成功交付复杂Web应用的关键。本文将深入探讨现代前端包管理器如npm, yarn和pnpm的工作原理,以及它们如何与模块化编程实践(例如CommonJS、ES6模块)协同工作以优化开发流程。我们将剖析这些工具的内部机制,了解它们如何解决依赖冲突,提高安装速度,并保证项目的健壮性。同时,本文还将介绍模块化编程的最佳实践,包括代码拆分、重用和版本控制,帮助开发者构建可维护且性能卓越的前端项目。
|
4月前
|
关系型数据库 MySQL Shell
CMake构建Makefile深度解析:从底层原理到复杂项目(三)
CMake构建Makefile深度解析:从底层原理到复杂项目
152 0
|
4月前
|
编译器 vr&ar C++
CMake构建Makefile深度解析:从底层原理到复杂项目(二)
CMake构建Makefile深度解析:从底层原理到复杂项目
201 0
|
4月前
|
编译器 Linux C语言
【CMake install目录解析】CMake 深度解析:实现精准、高效的项目构建与安装
【CMake install目录解析】CMake 深度解析:实现精准、高效的项目构建与安装
429 0
|
1月前
|
人工智能 PyTorch 算法框架/工具
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
【8月更文挑战第6天】Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
【热门开源项目】阿里开源巨擘:Qwen-2 72B深度解析与推荐
在人工智能的浪潮中,开源模型如同璀璨的星辰,指引着开发者们探索未知的领域。而今天,我们将聚焦在阿里云推出的开源模型Qwen-2 72B上,从其项目介绍、技术特点、代码解析等多个角度,深入解析并推荐这一卓越的开源项目。
87 1
|
28天前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
32 0
|
28天前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
69 0

热门文章

最新文章

推荐镜像

更多