一文搞懂JSON

目录

什么是JSON?

JSON的基本数据类型

JSON的特点和优势(了解)

JSON格式规范(重点)

JSON的基本操作

关键接口的梳理

序列化

反序列化

答案和解析

序列化答案

反序列化答案

第一种思路

第二种思路


什么是JSON?

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于将数据结构化地传输和存储。它由键值对组成,采用类似于JavaScript对象的格式来表示数据。JSON易于阅读和编写,并且易于解析和生成,成为广泛应用于Web应用程序和数据交换的标准格式之一。

JSON的基本数据类型

类型

解释

案例

字符

(String)

由双引号包围的Unicode字符序列

"Hello, World!"

数字(Number)

包括整数和浮点数

42,3.14

布尔值(Boolean)

表示真或假,C相关的实现库可能会用0和1表示

值为true或false

空值

(Null)

表示空值

值为null

对象

(Object)

由一组无序的键值对组成,键是字符串,值可以是任意的JSON数据类型。键值对之间使用逗号分隔,整个对象使用花括号{}包围

{"name": "John",

"age": 30,

"city": "New York"}

数组

(Array)

由一组有序的值组成,值可以是任意的JSON数据类型。值之间使用逗号分隔,整个数组使用方括号[]包围

[1, 2, 3, 4, 5]

JSON的特点和优势(了解)

  1. 简洁和易读:JSON使用简洁的文本格式表示数据,易于阅读和编写。
  2. 平台无关性:JSON是一种独立于编程语言和平台的数据格式,可被多种编程语言解析和生成。
  3. 易于解析和生成:JSON的解析和生成相对简单,各种编程语言都提供了相应的JSON解析器和生成器。
  4. 支持复杂数据结构:JSON支持嵌套、复杂的数据结构,可以表示对象、数组和各种组合类型的数据。
  5. 与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校验格式化工具(Be JSON)

JSON的基本操作

JSON的基本操作通常涉及以下几个方面:

  1. 创建JSON对象: 可以使用编程语言提供的函数、类或库来创建JSON对象。通常,这些函数或方法接受键值对作为参数,用于指定JSON对象的属性和对应的值。
  2. 解析JSON字符串: 将JSON字符串解析为相应的数据结构,如对象、数组或基本数据类型。编程语言提供相应的解析函数或方法,可以将JSON字符串转换为可操作的数据对象。
  3. 生成JSON字符串: 将数据对象转换为JSON字符串的表示形式,以便于传输、存储或与其他系统进行交互。编程语言提供相应的函数或方法,可以将数据对象转换为符合JSON格式规范的字符串。
  4. 访问和修改JSON对象的属性: 通过键访问JSON对象的属性,并可以对其进行修改。可以使用编程语言提供的API来访问、读取和修改JSON对象的属性值。
  5. 遍历JSON数组: 遍历JSON数组中的元素,逐个访问和处理数组中的数据项。使用循环结构来遍历数组,根据索引或迭代器获取数组中的每个元素。
  6. 嵌套JSON操作: 处理嵌套的JSON结构,包括访问、修改和操作嵌套的对象或数组。可以使用递归、循环等方法来处理嵌套的JSON结构。
  7. 序列化和反序列化(重点): 将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 使用详解_无痕眼泪的博客-CSDN博客由于c语言中,没有直接的字典,字符串数组等数据结构,所以要借助结构体定义,处理json。如果有对应的数据结构就方便一些, 如python中用json.loads(json)就把json字符串转变为内建的数据结构处理起来比较方便。一个重要概念:在cjson中,json对象可以是json,可以是字符串,可以是数字。。。cjson数据结构定义:#d..._cjsonhttps://blog.csdn.net/qq_32172673/article/details/88305781

https://www.cnblogs.com/liunianshiwei/p/6087596.htmlhttps://www.cnblogs.com/liunianshiwei/p/6087596.html

编译注意事项

编译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;
}

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
11-01 4万+
CAN(Controller Area Network,控制器局域网)总线协议是一种广泛应用于工业自动化、汽车电子等领域的串行通讯协议。其帧格式如下: <img src="https://img-blog.csdnimg.cn/20200925125252655.png" width="400"> CAN总线协议的帧分为标准帧和扩展帧两种,其中标准帧包含11位标识符,扩展帧包含29位标识符。在CAN总线上,所有节点都可以同时发送和接收数据,因此需要在帧中包含发送方和接收方的信息。 帧格式的具体解释如下: 1. 帧起始符(SOF):一个固定的位模式,表示帧的起始。 2. 报文控制(CTRL):包含几个控制位,如IDE、RTR等。其中IDE表示标识符的类型,0表示标准帧,1表示扩展帧;RTR表示远程请求帧,0表示数据帧,1表示远程请求帧。 3. 标识符(ID):11位或29位的标识符,用于区分不同的CAN消息。 4. 控制域(CTL):包含几个控制位,如DLC、EDL等。其中DLC表示数据长度,即数据域的字节数;EDL表示数据长度是否扩展,0表示标准数据帧,1表示扩展数据帧。 5. 数据域(DATA):0~8字节的数据。 6. CRC:用于校验数据是否正确。 7. 确认位(ACK):由接收方发送的确认信息,表示数据是否正确接收。 8. 结束符(EOF):一个固定的位模式,表示帧的结束。 以上就是CAN总线协议的帧格式。在实际应用中,节点之间通过CAN总线进行数据交换,通过解析帧中的各个字段,可以判断消息的发送方、接收方、数据内容等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值