1. 引言
这段时间做个项目,要加一个配置文件。配置文件之前有用过ini、yaml、lua,这次正好碰巧项目里还要加MQTT,要用到json,就直接拿json来做配置文件了。
其实json很多人是不建议做配置文件的,毕竟没有注释,格式对于手写也不是很友善,但是就当尝试一下新事物嘛,感受一下总是好的。
OK,开始。
2. Json格式
Json相比与ini文件来说,格式要复杂一些。虽然基本单元也是键值对。
要搞清楚几个名词:
- 键值
- 对象
- 数组
从参考链接1里摘了一段,基本上记住and理解这些就够了:
JSON对象是一个无序的"名称/值"键值对的集合:
- 以"{“开始,以”}"结束,允许嵌套使用;
- 每个名称和值成对出现,名称和值之间使用":"分隔;
- 键值对之间用","分隔
- 在这些字符前后允许存在无意义的空白符;
对于键值value,可以有如下值,也是json的基本数据类型:
- 一个新的json对象:使用"{“和”}"表示
- 数组:使用"[“和”]"表示
- 数字:直接表示,可以是整数,也可以是浮点数
- 字符串:使用引号"表示
- 布尔值:false、true中的一个(必须是小写)
- null
对于数组 array,就是键值(value)(注意不是键值对!)的集合
虽然就这么几句,真的理解还挺不容易的,写起来有时候也很容易犯错。
可能也是因为这个,才被觉得不太友善吧。
可以在 这里 做格式的规范和验证。
稍微解释一下键值类型,也就是基本的数据类型。
- 新的Json对象
这个是实现多层的一个重要机制,对象一层层对应下去,实现多层。
可见,"animal"对应的键值就是一个对象,即{“human”:{“age”:18}},
而"human"对应的键值也是一个对象,即{“age”:18},
最后"age"对应的键值是数值18.
{
“animal”:
{
“human”:
{
“age”: 18
}
}
}
- 数组
数组的使用是比较容易混的,尤其和对象混在一起,在 JSON 中,数组值的类型必须属于字符串、数字、对象、数组、布尔或 null。
e.g.
举例:Value为数组
{
“employees”: [
{ “firstName”:“Bill” , “lastName”:“Gates” },
{ “firstName”:“George” , “lastName”:“Bush” },
{ “firstName”:“Thomas” , “lastName”:“Carter” }
]
}
举例:value值为集合
{
“employees”: {
“employees1”:{ “firstName”:“Bill” , “lastName”:“Gates” },
“employees2”:{ “firstName”:“George” , “lastName”:“Bush” },
“employees3”:{ “firstName”:“Thomas” , “lastName”:“Carter” }
}
}
注意,不要写成
{
“employees”: {
{ “firstName”:“Bill” , “lastName”:“Gates” },
{ “firstName”:“George” , “lastName”:“Bush” },
{ “firstName”:“Thomas” , “lastName”:“Carter” }
}
}
这种,{ “firstName”:“Bill” , “lastName”:“Gates” } 、{ “firstName”:“George” , “lastName”:“Bush” } 和{ “firstName”:“Thomas” , “lastName”:“Carter” } 都是双引号包起来的,可见都是对象。
但是3个对象构成的集合,只能是数组,而不是一个新的对象。因此不能再用{}包括,要用[]。
数组可以是对象的集合。
但是对象不能是对象的集合,而是键值对的集合。
- 数字、字符串、布尔值、null
这几个没啥说的,该咋用咋用把。
数字包含 整数浮点数,可以直接写。
字符串用双引号包裹。
布尔值包括true 和 false。
3. cJson使用
cJson是一个使用纯c编写的json解析库,因为是纯C的,所以移植性非常好。同时文件结构十分简单,一个.c和一个.h即可,总共不到1k行,使用时包含头文件,就可以直接使用库接口。
cJson官方下载地址
3.1 核心结构体
/* 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. */
<span class="token keyword">int</span> type<span class="token punctuation">;</span> <span class="token comment">/* The type of the item, as above. */</span> <span class="token keyword">char</span> <span class="token operator">*</span>valuestring<span class="token punctuation">;</span> <span class="token comment">/* The item's string, if type==cJSON_String */</span> <span class="token keyword">int</span> valueint<span class="token punctuation">;</span> <span class="token comment">/* The item's number, if type==cJSON_Number */</span> <span class="token keyword">double</span> valuedouble<span class="token punctuation">;</span> <span class="token comment">/* The item's number, if type==cJSON_Number */</span> <span class="token keyword">char</span> <span class="token operator">*</span>string<span class="token punctuation">;</span> <span class="token comment">/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */</span>
} cJSON;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
下面单独介绍下每一项
- next 指向同级的下一项
- prev指向同级的上一项
- child指向下一级,当值为 数组 或者一个新的对象时,此指针不为空
- type,值的类型,可以在cJSON.h中找到定义:
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6 - valuestring:如果value是string,可以从这里读取
- valueint:如果value是整数,可以从这里读取
- valuedouble:如果value是浮点数,可以从这里读取
- string:key-value键对值中,key的名字
cJson的解析,并不是直接把整个json文件解析到一个结构体里,因为不同文件的结构不够,cJSON只是把最基本的单元–键值对进行解析,整个文件的实现需要人为的根据json的结构去遍历,因此十分通用。
3.2 重要接口
cJson使用起来比较简单,常用也就几个接口。基本上找个demo看一遍就清楚了。
举个简单的例子:
{
"a1": 1,
"b1": [
{ "b1_1": 2.1},
{ "b1_2": 2.2}
],
"c1": {
"c1_1": 3.1,
"c1_2": 3.2
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- cJSON_Parse
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */
extern cJSON *cJSON_Parse(const char *value);
- 1
- 2
cJSON_Parse可以解析整段json数据,注意这里入参是字符串,而不是文件路径,如果是读取配置文件,需要自己读取到字符串里,再传给cJSON_Parse。
cJSON_Parse会返回第一个键值对的指针。
假设字符串为上例,cJSON_Parse返回指针指向最外层{}内容,prev 是null,next 是null,child 指向"a1":1的键值对,type 为6(cJSON_Object)。很显然,{ “a1”: 1, “b1”: [
{ “b1_1”: 2.1}, { “b1_2”: 2.2} ], “c1”: {“c1_1”: 3.1, “c1_2”: 3.2}}是3个键值对的集合,类型是对象(object).
- cJSON_GetObjectItem
根据key名称来,返回当前入参指针所指键对值的某个键(Key)的value的指针(cJson类型)
/* Get item "string" from object. Case insensitive. */
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
- 1
- 2
如上例,如果cJSON的指针p指向"c1"所在键值对,此键值对的value类型为一个对象object,调用cJSON_GetObjectItem(p,“b1_1”),可以返回b1_1所在键值对的指针。
- cJSON_GetArraySize / cJSON_GetArrayItem
/* Returns the number of items in an array (or object). */
extern int cJSON_GetArraySize(cJSON *array);
/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */
extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
- 1
- 2
- 3
- 4
cJSON_GetArraySize 获取当前数组的成员个数。
如上例,如果cJSON的指针p指向"b1"所在键值对,此键值对的value类型为array数组,调用 cJSON_GetArraySize 可返回2。此数组有2个成员。
“b1”: [
{ “b1_1”: 2.1},
{ “b1_2”: 2.2}
],
同样,使用cJSON_GetArrayItem(p,0)返回指针指向{ “b1_1”: 2.1},使用cJSON_GetArrayItem(p,1)返回指针指向{ “b1_2”: 2.2},
3.3 遇到的问题
- 移植后第一次编译,报错:
cJSON.h:58:26: error: unknown type name ‘size_t’
解决方案:编译时不应该编译cJSON.h文件
-
找不到cJSON.h
把 #include <cJSON.h> 改成了 #include “cJSON.h” -
找不到floor和pow函数:
链接的时候加入 -lm ,添加math库就好。 -
cJSON_Parse失败,返回null
json的格式是没有问题的,就是使用cJSON_Parse解析一直返回null。
查了一下,是因为每一级的最后一个value后面不能加",",虽然有些格式化工具并不会报这个错。
e.g.
{
"a":1,
"b":2
}
- 1
- 2
- 3
- 4
可以解析,但是
{
"a":1,
"b":2,
}
- 1
- 2
- 3
- 4
- 5
就无法解析。