目录
什么是JSON?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于将数据结构化地传输和存储。它由键值对组成,采用类似于JavaScript对象的格式来表示数据。JSON易于阅读和编写,并且易于解析和生成,成为广泛应用于Web应用程序和数据交换的标准格式之一。
JSON的基本数据类型
类型 | 解释 | 案例 |
字符 (String) | 由双引号包围的Unicode字符序列 |
|
数字(Number) | 包括整数和浮点数 |
|
布尔值(Boolean) | 表示真或假,C相关的实现库可能会用0和1表示 |
|
空值 (Null) | 表示空值 |
|
对象 (Object) | 由一组无序的键值对组成,键是字符串,值可以是任意的JSON数据类型。键值对之间使用逗号分隔,整个对象使用花括号{}包围 |
|
数组 (Array) | 由一组有序的值组成,值可以是任意的JSON数据类型。值之间使用逗号分隔,整个数组使用方括号[]包围 |
|
JSON的特点和优势(了解)
- 简洁和易读:JSON使用简洁的文本格式表示数据,易于阅读和编写。
- 平台无关性:JSON是一种独立于编程语言和平台的数据格式,可被多种编程语言解析和生成。
- 易于解析和生成:JSON的解析和生成相对简单,各种编程语言都提供了相应的JSON解析器和生成器。
- 支持复杂数据结构:JSON支持嵌套、复杂的数据结构,可以表示对象、数组和各种组合类型的数据。
- 与Web应用程序兼容性良好:JSON广泛用于Web应用程序中,可以轻松地与JavaScript进行交互。
JSON格式规范(重点)
1. 数据使用键值对表示,键和值之间使用冒号(:)分隔。 例如:{"name": "John", "age": 30} 2. 键使用双引号(")包围,值可以是字符串、数字、布尔值、对象、数组或null。 例如:{"name": "John", "age": 30, "isStudent": true, "address": null} 3. 键值对之间使用逗号(,)分隔,最后一个键值对后不应有逗号。 例如:{"name": "John", "age": 30} 4. 字符串值使用双引号(")包围,可以包含任意Unicode字符序列,特殊字符可以使用转义字符表示。 例如:"Hello, World!", "I "love" JSON" 5. 数字可以是整数或浮点数,不使用引号包围。 例如:42, 3.14 6. 布尔值只有两个取值:true和false,不使用引号包围。 例如:true, false 7. 数组使用方括号([])包围,值之间使用逗号分隔。 例如:[1, 2, 3, 4, 5] 8. 对象使用花括号({})包围,键值对之间使用逗号分隔。 例如:{"name": "John", "age": 30} 9. JSON是严格区分大小写的,键和字符串值都应该使用双引号包围。 10. JSON可以嵌套,允许在对象中包含对象或数组,或在数组中包含对象或其他数组。 11. JSON不支持注释,不允许在JSON数据中添加注释。
下面是一个简单的JSON
{
"name": "southernbrid",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
分享个JSON格式校验网站
JSON的基本操作
JSON的基本操作通常涉及以下几个方面:
- 创建JSON对象: 可以使用编程语言提供的函数、类或库来创建JSON对象。通常,这些函数或方法接受键值对作为参数,用于指定JSON对象的属性和对应的值。
- 解析JSON字符串: 将JSON字符串解析为相应的数据结构,如对象、数组或基本数据类型。编程语言提供相应的解析函数或方法,可以将JSON字符串转换为可操作的数据对象。
- 生成JSON字符串: 将数据对象转换为JSON字符串的表示形式,以便于传输、存储或与其他系统进行交互。编程语言提供相应的函数或方法,可以将数据对象转换为符合JSON格式规范的字符串。
- 访问和修改JSON对象的属性: 通过键访问JSON对象的属性,并可以对其进行修改。可以使用编程语言提供的API来访问、读取和修改JSON对象的属性值。
- 遍历JSON数组: 遍历JSON数组中的元素,逐个访问和处理数组中的数据项。使用循环结构来遍历数组,根据索引或迭代器获取数组中的每个元素。
- 嵌套JSON操作: 处理嵌套的JSON结构,包括访问、修改和操作嵌套的对象或数组。可以使用递归、循环等方法来处理嵌套的JSON结构。
- 序列化和反序列化(重点): 将JSON对象序列化为字符串,或将JSON字符串反序列化为对象。序列化是将数据对象转换为JSON字符串,反序列化是将JSON字符串转换为数据对象。(本文主要介绍C语言的JSON库来进行序列化和反序列化)
下面是一个C库,用来完成本文教学
📎cJSON.chttps://www.yuque.com/attachments/yuque/0/2023/txt/35243076/1687951166715-b8398dec-d697-4507-b49e-1e2ea5595ce9.txt📎cJSON.hhttps://www.yuque.com/attachments/yuque/0/2023/txt/35243076/1687951166713-f66a28ef-8aaa-4fb0-8f7c-7fa49d94b608.txt
下面两篇文章也能帮助我们完成JSON序列化和JSON反序列化的操作
编译注意事项
编译cJSON库时候,gcc需要增加 -lm 选项,动态链接math库。
关键接口的梳理
/* 类型定义 */
#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
/* CJSON核心结构体 */
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; /* 节点的类型,取值是上面的6种 */
char *valuestring; /* 如果类型是cJSON_String,那么值会被存到这里 */
int valueint; /* 如果类型是cJSON_Number,且是整型,那么值会被存到这里 */
double valuedouble; /* 如果类型是cJSON_Number,且是浮点型,那么值会被存到这里 */
char *string; /* 键*/
} cJSON;
/****************************通用接口****************************/
//把传入的字符串转成cJSON的结构(反序列化)
cJSON *cJSON_Parse(const char *value);
//把cJSON结构转成字符串(序列化),调用后需要配合free接口释放malloc的内存
char *cJSON_Print(cJSON *item);
//无格式化序列化,节省内存,调用后需要配合free接口释放malloc的内存
char *cJSON_PrintUnformatted(cJSON *item);
//释放节点的内存,防止内存泄露
void cJSON_Delete(cJSON *c);
/****************************反序列化相关接口****************************/
//获取数组的大小
int cJSON_GetArraySize(cJSON *array);
//从array数组中获取第item个子节点
cJSON *cJSON_GetArrayItem(cJSON *array,int item);
//获取object大节点名字叫string的子节点
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
//判断object大节点中是否有名字叫string的小节点
int cJSON_HasObjectItem(cJSON *object,const char *string);
/****************************序列化相关接口****************************/
//创建一个普通节点
cJSON *cJSON_CreateObject(void);
//创建一个数组节点
cJSON *cJSON_CreateArray(void);
//把item节点增加到array的数组节点中
void cJSON_AddItemToArray(cJSON *array, cJSON *item);
//把item节点增加到object中作为子节点,item节点的键名为string
void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
//创建各种类型的cJSON节点
cJSON *cJSON_CreateNull(void);
cJSON *cJSON_CreateTrue(void);
cJSON *cJSON_CreateFalse(void);
cJSON *cJSON_CreateBool(int b);
cJSON *cJSON_CreateNumber(double num);
cJSON *cJSON_CreateString(const char *string);
cJSON *cJSON_CreateArray(void);
cJSON *cJSON_CreateObject(void);
序列化
使用cJSON库序列化出下面的JSON内容
{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
反序列化
使用cJSON库对下面的JSON文件进行反序列化操作。ver和login的子节点直接打印即可,data的数据节点解析后形成链表存储(将下面内容存为文件,读取后解析即可)。
{
"ver": "1.0",
"login": {
"user": "zhangsan",
"pwd": 1234
},
"data": [{
"key": 1,
"type": 2,
"val": "10"
},
{
"key": 2,
"type": 1,
"val": "0"
},
{
"key": 3,
"type": 3,
"val": "22.5"
}
]
}
注意:上面的所有val值都是string类型,解析出来存到节点前,需要根据type的类型来进行转换,按照相应的类型存储到共用体中。
提示:对于数据解析后,形成新的节点存放链表,最好的方式是为每个节点分配单独的内存。例:
struct data {
int key;
int type;
union val_t val;
struct list_head list;
};
struct data *node = (struct data *)malloc(sizeof(struct data));
node->xxx = xxx;
插入链表即可
答案和解析
序列化答案
#include <stdio.h>
#include "cJSON.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 序列化
cJSON *root = cJSON_CreateObject(); // 创建根节点
cJSON *age = cJSON_CreateNumber(14); // 创建一个数值类型的节点
cJSON_AddItemToObject(root, "age", age); // 将age节点添加到root对象中,键名为"age"
// 增加数组节点
cJSON *arr = cJSON_CreateArray(); // 创建一个数组类型的节点
cJSON_AddItemToArray(arr, cJSON_CreateString("JavaScript")); // 向数组中添加一个字符串节点
cJSON_AddItemToArray(arr, cJSON_CreateString("Java")); // 向数组中添加一个字符串节点
cJSON_AddItemToArray(arr, cJSON_CreateString("Python")); // 向数组中添加一个字符串节点
cJSON_AddItemToObject(root, "skills", arr); // 将数组节点添加到root对象中,键名为"skills"
// 输出内容
char *p = cJSON_PrintUnformatted(root); // 将JSON对象转换为字符串表示形式
printf("root = %s\n", p);
free(p);
cJSON_Delete(root); // 释放JSON对象占用的内存
return 0;
}
反序列化答案
第一种思路
#include <stdio.h>
#include "cJSON.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "list.h"
#include <string.h>
#define MAX 1024
union val_t
{
int i_val;
float f_val;
char *s_val[MAX];
};
struct data {
int key;
int type;
union val_t val;
struct list_head list;
};
int main(int argc, char const *argv[])
{
// 构造链表
struct list_head head;
INIT_LIST_HEAD(&head);
struct data mydate[10]; // 创建结构体数组
// 读取文件内容到缓冲区
int fd = open("data.json", O_RDONLY);
if (fd < 0)
{
perror("open err");
return -1;
}
char buf[MAX] = {0};
size_t len = read(fd, buf, MAX);
if (len < 0)
{
perror("open err");
return -1;
}
// 反序列化动作
cJSON *root = cJSON_Parse(buf); // 把传入的字符串转成cJSON的结构(反序列化)
if (root == NULL)
{
printf("cJSON_Parse err.");
return -1;
}
// 开始摘取数据
cJSON *item;
cJSON *keyitem;
cJSON *typeitem;
cJSON *valitem;
item = cJSON_GetObjectItem(root, "ver"); // 获取root的ver子节点
printf("%s : %s\n", item->string, item->valuestring);
cJSON *login = cJSON_GetObjectItem(root, "login");
cJSON *user = cJSON_GetObjectItem(login, "user");
cJSON *pwd = cJSON_GetObjectItem(login, "pwd");
printf("%s :\n", login->string);
printf("%s : %s\n", user->string, user->valuestring);
printf("%s : %d\n", pwd->string, pwd->valueint);
// 解析数组节点
cJSON *data;
data = cJSON_GetObjectItem(root, "data");
printf("%s :\n", data->string);
int count = cJSON_GetArraySize(data); // 获取数组大小用来进行遍历
for (size_t i = 0; i < count; i++)
{
cJSON *tmp = cJSON_GetArrayItem(data, i); // 获取数组节点中的第 i 个元素
keyitem = cJSON_GetObjectItem(tmp, "key");
typeitem = cJSON_GetObjectItem(tmp, "type");
valitem = cJSON_GetObjectItem(tmp, "val");
printf("key : %d\n", keyitem->valueint);
printf("type : %d\n", typeitem->valueint);
printf("val : %s\n", valitem->valuestring);
mydate[i].key = keyitem->valueint; // 设置key值
mydate[i].type = typeitem->valueint; // 设置类型
strcpy(mydate[i].val.s_val, valitem->valuestring); // 将值拷贝到val数组
list_add(&mydate[i].list, &head); // 将结构体添加到链表中
}
// 遍历链表并输出结果
struct list_head *pos;
struct data *newtmp;
list_for_each(pos, &head)
{
newtmp = list_entry(pos, struct data, list);
printf("key = %d, type = %d, val = %s\n", newtmp->key, newtmp->type, newtmp->val.s_val);
}
list_del(pos);
cJSON_Delete(root); // 释放JSON对象占用的内存
return 0;
}
第二种思路
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cJSON.h"
#include "list.h"
//定义共用体
typedef int BOOL;
union val_t
{
BOOL b_val; //bool类型存储空间
int i_val; //整形值存储空间
float f_val; //浮点值存储空间
};
//定义结构体
struct student
{
int key;
int type;
union val_t val;
struct list_head list;//指针域
};
int main(int argc, char const *argv[])
{
struct list_head head;
INIT_LIST_HEAD(&head);
char * jsonStr = "{\"ver\":\"1.0\",\"login\":{\"user\":\"zhangsan\",\"pwd\":1234},\"data\":[{\"key\":1,\"type\":2,\"val\":\"10\"},{\"key\":2,\"type\":1,\"val\":\"0\"},{\"key\":3,\"type\":3,\"val\":\"22.5\"}]}";
cJSON * root = NULL;
cJSON * item = NULL;//cjson对象
//把传入的字符串转成cJSON的结构(反序列化)
root = cJSON_Parse(jsonStr);//取出root对象(根节点)
if (!root)
{
printf("Error before: [%s]\n",cJSON_GetErrorPtr());
return -1;
}
else
{
printf("转换成功!\n");
//获取版本号--ver
item = cJSON_GetObjectItem(root, "ver");
printf("版本号%s:%s\n",item->string,item->valuestring);
//获取帐号密码
item = cJSON_GetObjectItem(root, "login");
cJSON * myuser = cJSON_GetObjectItem(item, "user");
printf("帐号%s:%s --- ",myuser->string,myuser->valuestring);
cJSON * mypwd = cJSON_GetObjectItem(item, "pwd");
printf("密码%s:%d\n",mypwd->string,mypwd->valueint);
cJSON * myData = cJSON_GetObjectItem(root, "data");//root节点下的data节点
item = myData->child;//item是数组或对象的子节点,即data节点下的每一个小的花括号节点,或者可以用cJSON *cJSON_GetArrayItem(cJSON *array,int item)获取数组中的子节点(花括号节点)
printf("data数据列表:\n");
while(item != NULL)
{
//取出data里的每一个花括号里的每一个小元素的值:即(key,type,val)的值
cJSON * mykey = cJSON_GetObjectItem(item, "key");
printf("键:%d ",mykey->valueint);
cJSON * mytype = cJSON_GetObjectItem(item, "type");
printf("类型:%d ",mytype->valueint);
cJSON * myval = cJSON_GetObjectItem(item, "val");//注意val的数据类型
printf("值:%s\n",myval->valuestring);
if(mykey != NULL && mytype != NULL && myval != NULL)
{
// printf("取值成功!以链表方式存储\n");
//将解析的数据放入链表中
struct student * stu = (struct student *)malloc(sizeof(struct student));//记得释放内存
stu->key = mykey->valueint;
stu->type = mytype->valueint;
if(mytype->valueint == 1)
{
int valnum = atoi(myval->valuestring);
stu->val.i_val = valnum;
}
else if(mytype->valueint == 2)
{
int valnum = atoi(myval->valuestring);
stu->val.i_val = valnum;
}
else if(mytype->valueint == 3)
{
int valnum = atof(myval->valuestring);
stu->val.f_val = valnum;
}
// stu->val.i_val = myval->valuestring;
//头插法插入
list_add(&stu->list,&head);
}
item = item->next;
}
//遍历打印
struct list_head *pos;//是指向一个结构体的一个指针域的指针
struct student *tmp;//是指向整个结构体的指针
// printf("init list\n");
printf("链表数据:\n");
list_for_each(pos, &head)
{
// sleep(3);
tmp = list_entry(pos, struct student, list);//可以从指针域偏移到整个结构体,拿到整个结构体的首地址,因此能够拿到属于该指针域的数据域
if(tmp->type == 1)
{
printf("key=%d, type=%d, val=%d\n", tmp->key, tmp->type, tmp->val.i_val);
}
else if(tmp->type == 2)
{
printf("key=%d, type=%d, val=%d\n", tmp->key, tmp->type, tmp->val.b_val);
}
else if(tmp->type == 3)
{
printf("key=%d, type=%d, val=%.2f\n", tmp->key, tmp->type, tmp->val.f_val);
}
}
printf("\n");
}
cJSON_Delete(root);
return 0;
}