目录
- 一、前言
- [二、JSON 是什么](#二、JSON 是什么)
- [三、JSON 的数据类型](#三、JSON 的数据类型)
- [四、C++ 处理 JSON:Jsoncpp](#四、C++ 处理 JSON:Jsoncpp)
- [五、C 处理 JSON:cJSON 库函数](#五、C 处理 JSON:cJSON 库函数)
- [六、cJSON 实战:解析嵌套 JSON](#六、cJSON 实战:解析嵌套 JSON)
- [七、JsonRPC 是什么](#七、JsonRPC 是什么)
- [八、JsonRPC 调用流程](#八、JsonRPC 调用流程)
- 九、总结
- 十、结尾
一、前言
大家好,这里是 Hello_Embed。上篇完成了 MQTT 的调试与容错优化,让客户端在弱网环境下更扛造。
本篇引入两个新概念:JSON 和 JsonRPC。在上位机软件框架中,前台 GUI 与后台控制中心通过网络通信,数据会以 JSON 格式封装成 JsonRPC 远程调用------这是本系列后半段的技术基础。
二、JSON 是什么
JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它独立于编程语言,几乎所有主流语言都支持 JSON 数据交换。
JSON 的核心结构是 键值对(name:value),用"名称"去索引对应的"值",类似 C 语言的字典或结构体成员。
解析:JSON 和 XML 都是常见的数据交换格式,但 JSON 更轻量------同一条数据,JSON 的字符数通常只有 XML 的 1/3~1/2,网络传输效率更高,这也是为什么现代 Web API 和上位机通信几乎都选 JSON。
三、JSON 的数据类型
JSON 的值(value)支持以下几种类型:
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "John" |
双引号包裹,支持转义字符 |
| 整数 | 30 |
不带小数点的数字 |
| 浮点数 | 4.5 |
带小数点的数字 |
| 布尔值 | true / false |
注意是全小写 |
| 空值 | null |
表示"无值" |
| 数组 | ["apple", "banana"] |
有序列表,元素类型可混 |
| 对象 | { "name": "John" } |
键值对集合,可嵌套 |
下面是一个包含多种类型的 JSON 示例:
json
{
"title": "JSON Example",
"author": {
"name": "John Doe",
"age": 35,
"isVerified": true
},
"tags": ["json", "syntax", "example"],
"rating": 4.5,
"isPublished": false,
"comments": null
}
这个例子展示了两层嵌套:author 是一个子对象,tags 是一个字符串数组。
解析 :在上位机场景中,传感器数据通常打包成 JSON 对象,比如
{ "temp": 25.6, "humid": 60 },传输效率高,解析也简单。
四、C++ 处理 JSON:Jsoncpp
上位机程序通常用 C++ 开发,业界最常用的 C++ JSON 库是 Jsoncpp。
官方仓库:https://github.com/open-source-parsers/jsoncpp
Jsoncpp 功能完整,支持从字符串解析 JSON、构建 JSON 对象、序列化回字符串,是上位机处理 JSON 的首选方案。
解析:上位机(PC 端)资源充足,用 Jsoncpp 这类功能完整的库没有问题。后续移植到嵌入式的 MQTT 部分,则更倾向用轻量的 cJSON。
五、C 处理 JSON:cJSON 库函数
在 C 语言环境下,可以使用 cJSON 库。相比 Jsoncpp,cJSON 更加轻量,适合资源受限的嵌入式场景。
cJSON 的核心使用流程如下:
5.1 解析:从字符串创建 cJSON 结构体
c
/* 从 JSON 字符串解析出 cJSON 结构体(类似 JSON.parse()) */
cJSON *cJSON_Parse(const char *value);
/* 直接创建一个空的 JSON 对象(类似 {}) */
cJSON *cJSON_CreateObject(void);
5.2 写入:往对象里添加数据
c
/* 添加各种类型的成员 */
cJSON_AddNullToObject(object, name); /* 添加 null */
cJSON_AddTrueToObject(object, name); /* 添加 true */
cJSON_AddFalseToObject(object, name); /* 添加 false */
cJSON_AddBoolToObject(object, name, boolean); /* 添加布尔值 */
cJSON_AddNumberToObject(object, name, number); /* 添加数字 */
cJSON_AddStringToObject(object, name, string); /* 添加字符串 */
cJSON_AddObjectToObject(object, name); /* 添加子对象 */
cJSON_AddArrayToObject(object, name); /* 添加数组 */
5.3 读取:根据键名或下标取值
c
/* 根据键名(key)获取成员,类似 JS 的 obj.name */
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);
/* 根据数组下标获取元素,类似 JS 的 arr[0] */
cJSON *cJSON_GetArrayItem(const cJSON *array, int index);
cJSON 结构体内部存储了三种数值形式:valuestring(字符串值)、valueint(整数值)、valuedouble(浮点数值),直接访问即可。
5.4 释放:删除 cJSON 结构体
c
/* 释放 cJSON 结构体及其子节点,避免内存泄漏 */
cJSON_Delete(cJSON *item);
解析 :cJSON 的设计思路是"一切皆节点"------数组和对象在 cJSON 眼里都是
cJSON *指针,遍历时用cJSON_GetArrayItem按下标取,用cJSON_GetObjectItem按键名取。这种统一结构让代码写起来很简洁。
六、cJSON 实战:解析嵌套 JSON
用一段测试代码演示完整的解析流程:
c
#include <stdio.h>
#include "cJSON.h"
int main(int argc, char** argv)
{
/* 待解析的 JSON 字符串(多行字符串字面量) */
const char* str = " \
{ \
\"title\": \"JSON Example\", \
\"author\": { \
\"name\": \"John Doe\", \
\"age\": 35, \
\"isVerified\": true \
}, \
\"tags\": [\"json\", \"syntax\", \"example\"], \
\"rating\": 4.5, \
\"isPublished\": false, \
\"comments\": null \
}";
/* 第一步:解析字符串 → cJSON 结构体 */
cJSON *json = cJSON_Parse(str);
if (!json) /* 解析失败返回 NULL */
{
printf("cJSON_Parse err\n");
return 0;
}
/* 第二步:取 "author" 子对象 */
cJSON *author = cJSON_GetObjectItem(json, "author");
/* 第三步:从 author 中取 "age",强转为整数值打印 */
cJSON *age = cJSON_GetObjectItem(author, "age");
if (age)
printf("age = %d\n", age->valueint); /* 输出:age = 35 */
/* 第四步:取 "tags" 数组,再按下标取第 3 个元素(index=2) */
cJSON *tags = cJSON_GetObjectItem(json, "tags");
cJSON *item = cJSON_GetArrayItem(tags, 2); /* 0-indexed,取第3个 */
if (item)
printf("item = %s\n", item->valuestring); /* 输出:item = example */
/* 第五步:解析完毕,释放内存 */
cJSON_Delete(json);
return 0;
}
运行结果:
age = 35
item = example
解析 :
cJSON_GetArrayItem是 0-indexed(从 0 开始),所以index=2取的是第三个元素"example"。解析完记得调用cJSON_Delete释放整个树,否则会内存泄漏。
七、JsonRPC 是什么
RPC(Remote Procedure Call,远程过程调用) 是一种通过网络调用另一台计算机上程序子进程的技术。对调用方来说,就像在调用本地函数一样,不需要关心网络细节。
在带 GUI 的上位机系统中,程序通常拆为前台 GUI 和后台控制中心两部分:
- 前台:负责界面显示
- 后台:负责数据处理、设备控制
- 两者通过网络通信,用 JsonRPC 封装调用
JsonRPC 是 RPC 的一种,以 JSON 作为数据格式,工作原理概括如下:
- 客户端像调用本地函数一样调用接口(传入方法名和参数)
- 客户端将调用信息组装成 JSON 数据,通过 socket 发送
- 服务端收到后反序列化(解码),执行对应函数
- 服务端将执行结果组装成 JSON,通过 socket 返回
- 客户端收到后解析 JSON,取出返回值
解析 :JsonRPC 的核心价值是前后端解耦------前台不需要知道后台是用什么语言实现,后台也可以独立升级。前后台通过"方法名 + 参数"的 JSON 约定来通信,和具体的编程语言无关。
八、JsonRPC 调用流程
以一个具体例子说明调用 subtract(42, 23) 的完整流程。假设客户端想调用远端的减法函数:
请求(Client → Server)
json
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
响应(Server → Client)
json
{"jsonrpc": "2.0", "result": 19, "id": 1}
其中:
jsonrpc:协议版本,固定为"2.0"method:想调用的远程方法名params:方法参数(可以是数组或对象)id:请求编号,用于匹配请求和响应
详细步骤拆解
| 步骤 | 角色 | 操作 |
|---|---|---|
| 1 | Client | 调用 rpc_sub(42, 23),内部构造 JSON 请求字符串 |
| 2 | Client | 用 send() 通过 socket 发送 JSON |
| 3 | Server | 用 recv() 收到 JSON 字符串 |
| 4 | Server | 调用 cJSON_Parse 解析出 method 和 params |
| 5 | Server | 根据 method 找到本地 local_sub(int v1, int v2) 并执行,得到 19 |
| 6 | Server | 构造结果 JSON {"jsonrpc":"2.0","result":19,"id":1} |
| 7 | Server | 用 send() 返回给 Client |
| 8 | Client | 解析响应 JSON,取出 result |
解析 :对 Client 调用者来说,只需要实现一个
rpc_sub函数,往 JSON 格式里填方法名和参数就行了,不需要知道 socket、序列化、反序列化这些底层细节------JsonRPC 库会帮你处理这些。
九、总结
| 知识点 | 要点 |
|---|---|
| JSON 格式 | 键值对(name:value),支持 7 种数据类型,独立于语言 |
| Jsoncpp | C++ 处理 JSON 的库,功能完整,适合上位机 PC 端 |
| cJSON 函数 | 解析(cJSON_Parse)/ 写入(cJSON_Add*ToObject)/ 读取(cJSON_GetObjectItem / GetArrayItem)/ 释放(cJSON_Delete) |
| cJSON 存储 | 三种值形式:valuestring、valueint、valuedouble,按需访问 |
| RPC 概念 | 远程调用,本地函数语法,网络透明 |
| JsonRPC | 以 JSON 为数据格式的 RPC,前后端解耦,独立升级 |
| JsonRPC 字段 | jsonrpc(版本)/ method(方法)/ params(参数)/ id(请求编号)/ result(返回值) |
十、结尾
本篇完成了 JSON 和 JsonRPC 的入门:了解了 JSON 的 7 种数据类型、cJSON 的增删改查函数,并通过一个完整的调用流程理解了 JsonRPC "本地调用语法 + JSON 网络传输" 的工作原理。
下一篇将实现 JsonRPC 的 Server 端,敬请期待~
Hello_Embed 继续带你从原理到实践,掌握嵌入式上位机开发的核心技能,敬请关注~
嵌入式上位机开发入门(二十八):JSON 与 JsonRPC 入门
目录
- 一、前言
- [二、JSON 是什么](#二、JSON 是什么)
- [三、JSON 的数据类型](#三、JSON 的数据类型)
- [四、C++ 处理 JSON:Jsoncpp](#四、C++ 处理 JSON:Jsoncpp)
- [五、C 处理 JSON:cJSON 库函数](#五、C 处理 JSON:cJSON 库函数)
- [六、cJSON 实战:解析嵌套 JSON](#六、cJSON 实战:解析嵌套 JSON)
- [七、JsonRPC 是什么](#七、JsonRPC 是什么)
- [八、JsonRPC 调用流程](#八、JsonRPC 调用流程)
- 九、总结
- 十、结尾
一、前言
大家好,这里是 Hello_Embed。上篇完成了 MQTT 的调试与容错优化,让客户端在弱网环境下更扛造。
本篇引入两个新概念:JSON 和 JsonRPC。在上位机软件框架中,前台 GUI 与后台控制中心通过网络通信,数据会以 JSON 格式封装成 JsonRPC 远程调用------这是本系列后半段的技术基础。
二、JSON 是什么
JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它独立于编程语言,几乎所有主流语言都支持 JSON 数据交换。
JSON 的核心结构是 键值对(name:value),用"名称"去索引对应的"值",类似 C 语言的字典或结构体成员。
解析:JSON 和 XML 都是常见的数据交换格式,但 JSON 更轻量------同一条数据,JSON 的字符数通常只有 XML 的 1/3~1/2,网络传输效率更高,这也是为什么现代 Web API 和上位机通信几乎都选 JSON。
三、JSON 的数据类型
JSON 的值(value)支持以下几种类型:
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "John" |
双引号包裹,支持转义字符 |
| 整数 | 30 |
不带小数点的数字 |
| 浮点数 | 4.5 |
带小数点的数字 |
| 布尔值 | true / false |
注意是全小写 |
| 空值 | null |
表示"无值" |
| 数组 | ["apple", "banana"] |
有序列表,元素类型可混 |
| 对象 | { "name": "John" } |
键值对集合,可嵌套 |
下面是一个包含多种类型的 JSON 示例:
json
{
"title": "JSON Example",
"author": {
"name": "John Doe",
"age": 35,
"isVerified": true
},
"tags": ["json", "syntax", "example"],
"rating": 4.5,
"isPublished": false,
"comments": null
}
这个例子展示了两层嵌套:author 是一个子对象,tags 是一个字符串数组。
解析 :在上位机场景中,传感器数据通常打包成 JSON 对象,比如
{ "temp": 25.6, "humid": 60 },传输效率高,解析也简单。
四、C++ 处理 JSON:Jsoncpp
上位机程序通常用 C++ 开发,业界最常用的 C++ JSON 库是 Jsoncpp。
官方仓库:https://github.com/open-source-parsers/jsoncpp
Jsoncpp 功能完整,支持从字符串解析 JSON、构建 JSON 对象、序列化回字符串,是上位机处理 JSON 的首选方案。
解析:上位机(PC 端)资源充足,用 Jsoncpp 这类功能完整的库没有问题。后续移植到嵌入式的 MQTT 部分,则更倾向用轻量的 cJSON。
五、C 处理 JSON:cJSON 库函数
在 C 语言环境下,可以使用 cJSON 库。相比 Jsoncpp,cJSON 更加轻量,适合资源受限的嵌入式场景。
cJSON 的核心使用流程如下:
5.1 解析:从字符串创建 cJSON 结构体
c
/* 从 JSON 字符串解析出 cJSON 结构体(类似 JSON.parse()) */
cJSON *cJSON_Parse(const char *value);
/* 直接创建一个空的 JSON 对象(类似 {}) */
cJSON *cJSON_CreateObject(void);
5.2 写入:往对象里添加数据
c
/* 添加各种类型的成员 */
cJSON_AddNullToObject(object, name); /* 添加 null */
cJSON_AddTrueToObject(object, name); /* 添加 true */
cJSON_AddFalseToObject(object, name); /* 添加 false */
cJSON_AddBoolToObject(object, name, boolean); /* 添加布尔值 */
cJSON_AddNumberToObject(object, name, number); /* 添加数字 */
cJSON_AddStringToObject(object, name, string); /* 添加字符串 */
cJSON_AddObjectToObject(object, name); /* 添加子对象 */
cJSON_AddArrayToObject(object, name); /* 添加数组 */
5.3 读取:根据键名或下标取值
c
/* 根据键名(key)获取成员,类似 JS 的 obj.name */
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);
/* 根据数组下标获取元素,类似 JS 的 arr[0] */
cJSON *cJSON_GetArrayItem(const cJSON *array, int index);
cJSON 结构体内部存储了三种数值形式:valuestring(字符串值)、valueint(整数值)、valuedouble(浮点数值),直接访问即可。
5.4 释放:删除 cJSON 结构体
c
/* 释放 cJSON 结构体及其子节点,避免内存泄漏 */
cJSON_Delete(cJSON *item);
解析 :cJSON 的设计思路是"一切皆节点"------数组和对象在 cJSON 眼里都是
cJSON *指针,遍历时用cJSON_GetArrayItem按下标取,用cJSON_GetObjectItem按键名取。这种统一结构让代码写起来很简洁。
六、cJSON 实战:解析嵌套 JSON
用一段测试代码演示完整的解析流程:
c
#include <stdio.h>
#include "cJSON.h"
int main(int argc, char** argv)
{
/* 待解析的 JSON 字符串(多行字符串字面量) */
const char* str = " \
{ \
\"title\": \"JSON Example\", \
\"author\": { \
\"name\": \"John Doe\", \
\"age\": 35, \
\"isVerified\": true \
}, \
\"tags\": [\"json\", \"syntax\", \"example\"], \
\"rating\": 4.5, \
\"isPublished\": false, \
\"comments\": null \
}";
/* 第一步:解析字符串 → cJSON 结构体 */
cJSON *json = cJSON_Parse(str);
if (!json) /* 解析失败返回 NULL */
{
printf("cJSON_Parse err\n");
return 0;
}
/* 第二步:取 "author" 子对象 */
cJSON *author = cJSON_GetObjectItem(json, "author");
/* 第三步:从 author 中取 "age",强转为整数值打印 */
cJSON *age = cJSON_GetObjectItem(author, "age");
if (age)
printf("age = %d\n", age->valueint); /* 输出:age = 35 */
/* 第四步:取 "tags" 数组,再按下标取第 3 个元素(index=2) */
cJSON *tags = cJSON_GetObjectItem(json, "tags");
cJSON *item = cJSON_GetArrayItem(tags, 2); /* 0-indexed,取第3个 */
if (item)
printf("item = %s\n", item->valuestring); /* 输出:item = example */
/* 第五步:解析完毕,释放内存 */
cJSON_Delete(json);
return 0;
}
运行结果:
age = 35
item = example
解析 :
cJSON_GetArrayItem是 0-indexed(从 0 开始),所以index=2取的是第三个元素"example"。解析完记得调用cJSON_Delete释放整个树,否则会内存泄漏。
七、JsonRPC 是什么
RPC(Remote Procedure Call,远程过程调用) 是一种通过网络调用另一台计算机上程序子进程的技术。对调用方来说,就像在调用本地函数一样,不需要关心网络细节。
在带 GUI 的上位机系统中,程序通常拆为前台 GUI 和后台控制中心两部分:
- 前台:负责界面显示
- 后台:负责数据处理、设备控制
- 两者通过网络通信,用 JsonRPC 封装调用
JsonRPC 是 RPC 的一种,以 JSON 作为数据格式,工作原理概括如下:
- 客户端像调用本地函数一样调用接口(传入方法名和参数)
- 客户端将调用信息组装成 JSON 数据,通过 socket 发送
- 服务端收到后反序列化(解码),执行对应函数
- 服务端将执行结果组装成 JSON,通过 socket 返回
- 客户端收到后解析 JSON,取出返回值
解析 :JsonRPC 的核心价值是前后端解耦------前台不需要知道后台是用什么语言实现,后台也可以独立升级。前后台通过"方法名 + 参数"的 JSON 约定来通信,和具体的编程语言无关。
八、JsonRPC 调用流程
以一个具体例子说明调用 subtract(42, 23) 的完整流程。假设客户端想调用远端的减法函数:
请求(Client → Server)
json
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
响应(Server → Client)
json
{"jsonrpc": "2.0", "result": 19, "id": 1}
其中:
jsonrpc:协议版本,固定为"2.0"method:想调用的远程方法名params:方法参数(可以是数组或对象)id:请求编号,用于匹配请求和响应
详细步骤拆解
| 步骤 | 角色 | 操作 |
|---|---|---|
| 1 | Client | 调用 rpc_sub(42, 23),内部构造 JSON 请求字符串 |
| 2 | Client | 用 send() 通过 socket 发送 JSON |
| 3 | Server | 用 recv() 收到 JSON 字符串 |
| 4 | Server | 调用 cJSON_Parse 解析出 method 和 params |
| 5 | Server | 根据 method 找到本地 local_sub(int v1, int v2) 并执行,得到 19 |
| 6 | Server | 构造结果 JSON {"jsonrpc":"2.0","result":19,"id":1} |
| 7 | Server | 用 send() 返回给 Client |
| 8 | Client | 解析响应 JSON,取出 result |
解析 :对 Client 调用者来说,只需要实现一个
rpc_sub函数,往 JSON 格式里填方法名和参数就行了,不需要知道 socket、序列化、反序列化这些底层细节------JsonRPC 库会帮你处理这些。
九、总结
| 知识点 | 要点 |
|---|---|
| JSON 格式 | 键值对(name:value),支持 7 种数据类型,独立于语言 |
| Jsoncpp | C++ 处理 JSON 的库,功能完整,适合上位机 PC 端 |
| cJSON 函数 | 解析(cJSON_Parse)/ 写入(cJSON_Add*ToObject)/ 读取(cJSON_GetObjectItem / GetArrayItem)/ 释放(cJSON_Delete) |
| cJSON 存储 | 三种值形式:valuestring、valueint、valuedouble,按需访问 |
| RPC 概念 | 远程调用,本地函数语法,网络透明 |
| JsonRPC | 以 JSON 为数据格式的 RPC,前后端解耦,独立升级 |
| JsonRPC 字段 | jsonrpc(版本)/ method(方法)/ params(参数)/ id(请求编号)/ result(返回值) |
十、结尾
本篇完成了 JSON 和 JsonRPC 的入门:了解了 JSON 的 7 种数据类型、cJSON 的增删改查函数,并通过一个完整的调用流程理解了 JsonRPC "本地调用语法 + JSON 网络传输" 的工作原理。
下一篇将实现 JsonRPC 的 Server 端,敬请期待~
Hello_Embed 继续带你从原理到实践,掌握嵌入式上位机开发的核心技能,敬请关注~