json
介绍
C语言json解释器。包含json文本文件解析和生成,占用空间小、安全高效、简洁灵活,能无差别或者小修改移植到大部分的C语言平台。
使用例子
生成
测试代码
void test_dump(void)
{
json_t json, t;
/* create root node */
json = json_create_object(NULL);
/* Add to root node */
json_add_string_to_object(json, "name", "json parser");
json_add_string_to_object(json, "version", "1.6.0");
json_add_string_to_object(json, "description", "This is a C language version of json streamlined parser.");
json_add_string_to_object(json, "repository", "https://gitee.com/Lamdonn/json");
/* Add an empty array to the root node */
t = json_add_array_to_object(json, "keywords"); /* t receive added array */
json_add_string_to_array(t, "json");
json_add_string_to_array(t, "streamlined");
json_add_string_to_array(t, "parser");
/* Add an empty object to the root node */
t = json_add_object_to_object(json, "others"); /* t receive added object */
json_add_bool_to_object(t, "open", JSON_TRUE);
json_add_string_to_object(t, "license", "GPL3.0");
/* Dump JSON objects to a file */
json_file_dump(json, "test.json");
/* Delete after end of use */
json_delete(json);
}
生成文件名: test.json
{
"name": "json parser",
"version": "1.6.0",
"description": "This is a C language version of json streamlined parser.",
"repository": "https://gitee.com/Lamdonn/json",
"keywords": ["json", "streamlined", "parser"],
"others": {
"open": true,
"license": "GPL3.0"
}
}
解析
解析前面生成的文件: test.json
测试代码
void test_load(void)
{
json_t json, t;
/* Load json file */
json = json_file_load("test.json");
if (!json) return;
t = json_to_key(json, "name");
if (json_isstring(t)) printf("module name: %s\r\n", json_value_string(t));
t = json_to_key(json, "others", "open");
if (json_isbool(t)) printf("open: %s\r\n", json_value_bool(t) ? "yes" : "no");
t = json_to_index(json, 4, 1);
if (json_isstring(t)) printf("keywords[1]: %s\r\n", json_value_string(t));
/* Delete after end of use */
json_delete(json);
}
打印结果
module name: json parser
open: yes
keywords[1]: streamlined
json语法
json语法是JavaScript对象表示语法的子集。
- 数据在
键值对
中,键值用:
指示 - 数据由逗号
,
分隔 - 使用斜杆
\
来转义字符 - 中括号
[]
保存数组,数组可以包含多个不同类型的值 - 大括号
{}
保存对象,对象可以包含多个键值对
json数据类型
#define JSON_TYPE_NULL (1) /* base type, null */
#define JSON_TYPE_BOOL (2) /* base type, bool */
#define JSON_TYPE_NUMBER (3) /* base type, number */
#define JSON_TYPE_STRING (4) /* base type, string */
#define JSON_TYPE_ARRAY (5) /* extension type, array */
#define JSON_TYPE_OBJECT (6) /* extension type, object */
json键值对
键值对书写方式
"key" : "value"
其中键的类型为字符串类型,需要用双引号""
括住。
而值可以是以下基本类型的任意一种
- 空类型(
null
) - 布尔类型(
true
、false
) - 数字类型(整数或浮点数)
- 字符串类型(在双引号
""
中) - 数组类型(在中括号
[]
中) - 对象类型(在大括号
{}
中)
语法例子
{
"information": {
"module": "json",
"history": [1.0, 1.1, 1.2, 1.21, 1.3, 1.31, 1.32, 1.42],
"paser": true,
"print": false
},
"other": null
}
操作方法
结构体
/* json define */
typedef struct _JSON_ {
/* link */
struct _JSON_* next; /* protected, not readable and writable */
/* information */
int info; /* protected, readable only */
/* The space behind the structure is variable key and value */
/* [char *key] */
/* [int value / double value / char* value / json_t child] */
} JSON, *json_t;
在json语法当中,数据以键值对的形式存储(在数组中存储时,没有键)。
在此json解析器中,存储的最小单元,也是唯一的存储单元为上面指示的JSON结构体,而在此结构体一共包含了4个成员,*next指针、info、*key(隐式成员)、value(隐式成员),其中的键值对为隐式成员,因为并不是所有的JSON对象都需要键(比如数组的对象),值也不是固定类型的成员,类型可以根据info
信息配置为int、double、*char等类型。采用这种隐式的结构体成员的方式,可以大大的节省空间。
*next成员
JSON结构体为唯一的存储单元,在存储结构上以单向链表的形式进行存储和维护。
info成员
info成员位int型,低八位存储json对象的基本类型(null、bool、数字、字符串、数组、对象),第9位存储bool类型的值(当基本类型为bool型时起效,1为true,0为false),第10位存储数字整型标志(当基本类型为数字型起效,1为整型,0为浮点型),第10位标记此json对象是否有键(1为有键,0为无键),其余位保留。
info成员的信息,会决定创建json对象时候,隐式参数是否创建,比如JSON_WITH_KEY位起效的话,则创建时候预留char* key的空间,比如根据不同的基本类型决定隐式参数value的类型(为int、double、char*、json_t的哪一个,或者没有value)。
key成员
key成员为隐式不定的,根据实际存储类型而决定有没有,key为字符串,不具备去重,也就是同一级的json对象可以存在相同的key,具体访问到哪个key,就得取决于查找的方式,本json解析器采用单向链表的存储结构,访问会访问到同名的第一个对象。
value成员
value成员为隐式不定的,根据info的基本类型来决定在结构体尾部追加相应类型所需的空间,比如整型,则在后面追加**sizeof(int)**大小的空间存储整型数据。
常用方法
json解析
方法原型
json_t json_loads(const char* text); // 加载文本
json_t json_file_load(char* filename); // 加载文件
json_loads
函数传进json文本信息,则可以返回解析出来的json对象句柄。 json_file_load
函数则是直接传入文件名即可加载文件返回json对象,函数内部通过C语言标准文件操作函数集对文件进行读取,然后套用json_loads
函数进行解析,支持utf8编码文件。
json生成
方法原型
char* json_dumps(json_t json, int preset, int unformat, int* len); // 生成文本
int json_file_dump(json_t json, char* filename); // 生成文件
json_dumps
函数将json对象转换成文本信息,其中preset
为预置的文本长度,预置的长度和最终输出文本长度接近则可以减小内存重分配的次数而提高转换效率;unformat
是否不采用格式化输出,不采用格式化则文本会挤在一行;len
是转换的输出长度。
json_file_dump
函数套用了json_dumps
函数将文本信息存储到指定名字的文件。
json获取子对象
方法原型
json_t json_get_child(json_t json, const char* key, int index);
#define json_to_index(json, i, ...)
#define json_to_key(json, key, ...)
在json对象中,key是不具备查重的,也就是在同一个层级的json中,可能存在多个同名的key,json_get_child
方法则是可以用于匹配特定的key。此函数,当key
传入NULL时,则只有index起作用,按照索引来匹配子对象,当key
不为NULL的时候,则只会匹配相应key的子对象,并通过index来指示匹配第几个名为key
的对象。
t = json_get_child(json, NULL, 3); // 找索引为3的子对象
t = json_get_child(json, "a", 3); // 找键为"a"索引为3的子对象
json_to_index
和json_to_key
这两个方法都可以很方便的获取到子对象,同时可以当做查找方法查找是否存在相应的子对象。
json_to_index
方法通过索引的方式去获取子对象,不管对象类型是数组的还是对象的,此方法都能使用。
json_to_key
方法通过键的方式去获取子对象,但是此方法只适用于是对象类型的对象,因为数组没有键。
这两个方法的参数都带有了不定参数,这个不定参数可以输入若干个索引或者键去连续获取下个层级的子对象。如下例子:
t = json_to_key(json, "information", "module", "name");
等同
t = json_to_key(json, "information");
t = json_to_key(t, "module");
t = json_to_key(t, "name");
json获取对象的值类型
方法原型
#define json_type(json) // 获取类型
#define json_isnull(json) // 判断是不是空类型
#define json_isbool(json) // 判断是不是布尔类型
#define json_isnumber(json) // 判断是不是数字类型
#define json_isint(json) // 判断是不是数字整型
#define json_isfloat(json) // 判断是不是数字浮点型
#define json_isstring(json) // 判断是不是字符串型
#define json_isarray(json) // 判断是不是数组型
#define json_isobject(json) // 判断是不是对象型
json获取对象的键和值
方法原型
const char* json_key(json_t json);
int json_value_bool(json_t json);
int json_value_int(json_t json);
double json_value_float(json_t json);
const char* json_value_string(json_t json);
json_t json_value_array(json_t json);
json_t json_value_object(json_t json);
在获取值对值操作之前,建议先判断一下是不是期待的类型,如果操作不对的类型,可能破坏json的存储结构甚至导致程序奔溃。
json创建对象
方法原型
json_t json_create_null(char* key);
json_t json_create_bool(char* key, int b);
json_t json_create_int(char* key, int num);
json_t json_create_float(char* key, double num);
json_t json_create_string(char* key, const char* string);
json_t json_create_object(char* key);
json_t json_create_array(char* key);
此类方法可以创建json的基本类型,数组和对象又可以存储json对象,所以默认都创建为空的数组和空的对象,除了这两个,其他方法都可以在创建时候指定初始化值。
创建对象指定了key
,如果传入key则创建出来的对象可以添加到对象类型中,如果传入空则创建出来的对象可以添加到数组类型中。
数组可以存储任意类型的数据,但是一般都是存储同种类型的数据,因此数组的创建方法额外提供了初始化的方法。
json_t json_create_array_int(char* key, const int* numbers, int count);
json_t json_create_array_float(char* key, const float* numbers, int count);
json_t json_create_array_double(char* key, const double* numbers, int count);
json_t json_create_array_string(char* key, const char** strings, int count);
按照C语言数组,初始化数据到json数组。
json删除对象
方法原型
void json_delete(json_t json);
删除json自身及其所有展开子对象。json需要是独立的,也就是只能是根节点。
json链结和断链对象
方法原型
json_t json_attach(json_t json, int index, json_t item);
json_t json_detach(json_t json, char *key, int index);
json_attach
方法是将创建后的对象按照索引链结到另一个对象中,而被链结的对象其值类型必须为数组型或者对象型才可以。成功返回item自身,失败则是NULL。
json_detach
方法是将数组或者对象中,按照json_get_child
同样的key
和index
配合匹配逻辑,将指定的子对象断链出来,返回其子对象,失败则返回NULL。
这两个方法都不涉及对象的创建或者删除,只是存储结构的调整,通过配合其他方法实现添加或者移除的操作。
而插入的索引,除了插到指定索引中,还可以指定常用的头插和尾插。
#define JSON_HEAD
#define JSON_TAIL
json添加对象
#define json_add_null_to_array(json)
#define json_add_bool_to_array(json, b)
#define json_add_int_to_array(json, n)
#define json_add_float_to_array(json, n)
#define json_add_string_to_array(json, s)
#define json_add_array_to_array(json)
#define json_add_object_to_array(json)
#define json_add_null_to_object(json, key)
#define json_add_bool_to_object(json, key, b)
#define json_add_int_to_object(json, key, n)
#define json_add_float_to_object(json, key, n)
#define json_add_string_to_object(json, key, s)
#define json_add_array_to_object(json, key)
#define json_add_object_to_object(json, key)
这些方法是通过创建方法和链结方法配合而成,将特定类型的数据添加到array或者object型的json对象中。
json移除子对象
#define json_erase(json, key, index)
#define json_erase_by_index(json, index)
#define json_erase_by_key(json, key)
这些方法是通过删除方法和断链方法配合而成,移除array或者object中特定的子对象。
json对象修改
方法原型
int json_set_key(json_t json, const char* key);
int json_set_bool(json_t json, int b);
int json_set_int(json_t json, int num);
int json_set_float(json_t json, double num);
int json_set_string(json_t json, const char* string);
如果数值类型不是数组和对象时候,可以根据当前是什么类型而调用相应的set方法进行直接的修改。但是,当原始类型与修改类型不一致时候,则需要通过其上一级对象调用replace方法进行修改。
json替换
json_t json_replace(json_t json, char *key, int index, json_t item);
这个方法是无法用set方法进行修改时的后备方法。通过父级,匹配子对象,将匹配的子对象用新的对象去替换。
json对象复制
方法原型
json_t json_duplicate(json_t json);
根据源json对象深拷贝出一份json对象
json配置内存钩子函数
方法原型
typedef void* (*malloc_t)(size_t size);
typedef void (*free_t)(void* block);
typedef void* (*realloc_t)(void* block, size_t size);
int json_set_hooks(malloc_t _malloc, free_t _free, realloc_t _realloc);
C语言在嵌入式应用普遍,针对不同嵌入式平台,内存分配和释放函数都是一样的接口,此方法可以根据不同平台设置相应的内存钩子函数,默认已经使用了标准的分配和释放函数。
json_set_hooks
必须指定配套malloc
和free
,realloc
没有的话可以不指定(json内置通过malloc
和free
模拟realloc
),否则设置失败。
json解析报错
方法原型
int json_error_info(int* line, int* column);
此json解析器具备较为精准的报错机制,在执行json_loads
类加载函数时候,返回空值表明解析出错时候则可以调用json_error_info
方法来查看具体的错误信息,json_file_load
函数内部已经输出错误信息。
参数中,*line
为输出的错误行;*column
为输出的错误列,返回值则为错误类型。
如下例子,在false前面多了一个负号的错误
{
"isupdate": -false,
"version": "V1.0.1",
}
加载此文件出现一下报错,表明了第2行第15列在false附近出现了值类型的错误
Expecting value: line 2 column 15 near: [false].
错误类型包含以下几种
#define JSON_E_OK // 没有错误
#define JSON_E_INVALID // 无效的错误
#define JSON_E_GRAMMAR // 语法错误
#define JSON_E_END // 解析结尾出现了无效字符错误
#define JSON_E_KEY // 键错误
#define JSON_E_VALUE // 值错误
#define JSON_E_MEMORY // 内存错误
最后
仓库链接
如果喜欢希望点赞支持,如有bug或者好的建议欢迎留言交流。