嵌入式操作系统 | JSON 与 json-c:生成与解析
本课程开源地址(Gitee) :https://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git
课件、示例代码与验收脚本都在该仓库,可直接
git clone或下载 ZIP 使用。
模块六 · 2 学时 · 环境:VMware + Ubuntu 20.04
一句话概述:从上一课 ubus 打出的 JSON 讲起,一条命令一步地学 JSON:先看懂格式,再用 json-c 生成一份多传感器数据写成文件,接着解析字符串和文件,最后用 Python 的 json 标准库反向校验,配套课堂练习与课后作业。
本节目标
- 能看懂 JSON 的结构(对象、数组、键值对),知道它比自定义文本好在哪里
- 会用 json-c 生成 JSON(对象/数组/字符串/数字/布尔)并写入文件
- 会用 json-c 解析 JSON(从字符串、从文件),取出字段打印出来
一、讲解内容
1.1 知识讲解
JSON 就是"带名字的数据"。 上一课 ubus call 打出来的那一段就是 JSON:用 {} 表示对象(键值对),用 [] 表示数组,值可以是字符串、数字、true/false、还可以再嵌套对象数组。它的好处是人能看懂、机器好解析、跨语言通用(C、Python、JavaScript 都支持),所以设备上报、前后端交互都用它。
本课要生成的数据长这样:
json
{
"device":"ubuntu-gateway",
"uptime_s":1200,
"online":true,
"sensors":[
{ "name":"温度", "value":25.6, "unit":"C" },
{ "name":"湿度", "value":58.2, "unit":"%RH" }
]
}
json-c 常用接口(够用一辈子的那几个):
| 函数 | 作用 |
|---|---|
json_object_new_object() / json_object_new_array() |
建一个对象 / 数组 |
json_object_object_add(root, "键", 值对象) |
往对象里放一个键值对 |
json_object_array_add(arr, 元素) |
往数组里追加一个元素 |
json_object_new_string / _int / _double / _boolean |
造各种类型的值 |
json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY) |
转成整齐的 JSON 字符串 |
json_tokener_parse(字符串) |
把 JSON 字符串解析成对象 |
json_object_from_file(文件名) |
直接从文件读 JSON |
json_object_object_get_ex(root, "键", &v) |
从对象里取出某个键的值 |
json_object_get_string / _int / _double / _boolean(v) |
取值 |
json_object_put(root) |
释放(json-c 是引用计数,用完要还) |
注意一个现象:浮点数打印得"啰嗦"。 C 程序里写 25.6,生成的 JSON 里可能是 25.600000000000001。这是二进制浮点数的正常现象,不是错误 ;Python 用 json.load 读进来还是 25.6(本课最后一步会看到)。
1.2 操作步骤(一条命令一步,看现象)
第 1 步:编译(-ljson-c 是因为用了 json-c 库)
shell
cd ~/lab/课时21-JSON与json-c
gcc -Wall -Wextra -o json_demo json_demo.c -ljson-c
现象:无输出即成功。json-c 是课时 15 用 apt install libjson-c-dev 装上的(库在系统目录,不用自己编)。
第 2 步:先用 Python 造一个 JSON 文件给 C 程序解析(终端 A)
shell
python3 json_check.py make
cat cmd.json
现象:
text
已生成 cmd.json:
{
"cmd": "set",
"interval": 7,
"enable": true,
"target": "127.0.0.1"
}
第 3 步:C 程序解析这个文件(终端 B)
shell
./json_demo read cmd.json
现象:
text
③ 解析文件 cmd.json:
{
"cmd":"set",
"interval":7,
"enable":true,
"target":"127.0.0.1"
}
取到 interval = 7(下一步可以拿它当采集间隔)
第 4 步:C 程序生成 JSON 并写文件
shell
./json_demo
现象(① 生成、② 解析内嵌字符串,最后提示文件已写):
text
① 生成的 JSON(整齐格式):
{
"device":"ubuntu-gateway",
"uptime_s":1200,
"online":true,
"sensors":[
{
"name":"温度",
"value":25.600000000000001,
"unit":"C"
},
{
"name":"湿度",
"value":58.200000000000003,
"unit":"%RH"
}
]
}
已写入文件:report.json
② 解析这段 JSON:{"cmd":"set","interval":7,"enable":true}
cmd = set
interval = 7
enable = true
第 5 步:用 Python 反向校验 C 生成的 JSON(换一种语言读它)
shell
python3 json_check.py check
现象(Python 的 json 标准库读进来,逐项核对字段):
text
校验 report.json:
[通过] device = ubuntu-gateway
[通过] uptime_s = 1200
[通过] sensors 是数组且有 2 项
[通过] 传感器 温度 的 value = 25.6 C
[通过] 传感器 湿度 的 value = 58.2 %RH
全部通过 ✅
注意最后两行 :C 那边打印的是 25.600000000000001,Python 读出来是 25.6------JSON 的数值本来就是十进制表示,这点差别不影响使用。
第 6 步:自己动手改一改。 在 build_and_save() 里加第三个传感器(电量,batt),或者把 device 改成自己的学号,重新编译运行,再用 Python 校验一次。
1.3 常见错误与排查
| 现象 | 原因 | 解决 |
|---|---|---|
gcc: cannot find -ljson-c |
没装 json-c 开发包 | sudo apt install -y libjson-c-dev |
json-c/json.h: No such file or directory |
同上 | 同上 |
json_tokener_parse 返回空 |
JSON 字符串格式不对(少引号/逗号) | 把字符串打印出来一个字符一个字符对;用 json_check.py 生成合法 JSON |
读文件失败 读文件失败: cmd.json |
文件不在当前目录 | 先 python3 json_check.py make;确认 ls -l cmd.json |
| 内存一直涨 | 忘了 json_object_put() |
用完的对象要释放(引用计数) |
Python 校验报 不是合法 JSON |
C 程序写文件时没写完就退出 | 检查 fclose;看 cat report.json 内容是否完整 |
二、学生练习
本节命令速查表:
| 命令 | 作用 | 示例 |
|---|---|---|
| gcc ... -ljson-c | 编译 json-c 程序 | gcc -Wall -Wextra -o json_demo json_demo.c -ljson-c |
| python3 json_check.py make | 生成 cmd.json | python3 json_check.py make |
| ./json_demo read 文件 | 解析指定 JSON 文件 | ./json_demo read cmd.json |
| ./json_demo | 生成 report.json | ./json_demo |
| python3 json_check.py check | 校验 report.json | python3 json_check.py check |
| cat 文件 | 看 JSON 内容 | cat report.json |
| 编号 | 任务 | 提示 | 预期结果 |
|---|---|---|---|
| L21.1 | 按步骤跑通双向 | 第 2~5 步 | Python 生成 → C 解析;C 生成 → Python 校验通过 |
| L21.2 | 加一个传感器(电量) | 仿照温度那三行照抄改值 | report.json 里 sensors 有 3 项,校验脚本提示 3 项 |
| L21.3 | 把 device 改成自己的学号 | 改字符串 | Python 校验的 device 项会"失败"------正好体会校验脚本在查什么 |
| L21.4 | 手工把 cmd.json 改坏 | 比如删掉一个逗号,再 ./json_demo read cmd.json |
看到解析失败/字段读不到 |
| L21.5 | 用 jshn 看 JSON |
`echo '{"a":1}' | jshn` 之类试一下 |
| L21.6 | 用 Python 生成更复杂的 JSON | 加一个嵌套对象 | C 程序能用 json_object_object_get_ex 取到 |
三、作业
- 必做:完成 L21.1、L21.2,提交三张截图(
./json_demo输出 /cat report.json/ Python 校验通过),并回答:① JSON 里对象和数组分别怎么写?② 为什么 C 打印的浮点数和 Python 读到的看起来不一样?③ 为什么要json_object_put? - 选做:把"配置"做成 JSON 文件:写一个 C 程序读
cmd.json里的interval,然后按这个间隔在屏幕上打印计数(时间间隔就用课时 18 的 uloop 定时器) - 提交:截图 + 文字回答,命名
课时21-作业截图.png
四、本节小结与下节预告
本节学会了 JSON 的生成与解析:对象数组怎么造、怎么写成文件、怎么从字符串和文件里读回来,并用 Python 做了交叉校验。下节进入模块六的最后一课------串口:虚拟机里没有真串口,我们用 socat 造一对虚拟串口,用 Modbus RTU 协议把数据读回来(CRC16 正好复用课时 10 的代码)。
附录:怎么运行 + 完整代码
一个课件就够用:先按附录 A 的顺序敲命令(服务端/对端先开,自己的程序后开),再按附录 B 抄代码。行尾请用 LF。
附录 A|怎么运行(注意先后顺序)
shell
gcc -Wall -Wextra -o json_demo json_demo.c -ljson-c
python3 json_check.py make # ① 生成 cmd.json
./json_demo read cmd.json # ② C 解析这个文件
./json_demo # ③ C 生成 report.json + 解析内嵌 JSON
python3 json_check.py check # ④ Python 校验 report.json
附录 B|文件 json_demo.c
c
/*
* json_demo.c ------ 课时 21:用 json-c 生成和解析 JSON
*
* 编译:gcc -Wall -Wextra -o json_demo json_demo.c -ljson-c
* 运行:./json_demo ① 生成 JSON(打印 + 存成 report.json)
* ② 解析一段固定 JSON(打印字段)
* ./json_demo read 文件名 只解析指定的 JSON 文件
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <json-c/json.h>
/* ① 用 json-c 造一份多传感器上报数据,打印出来并存成文件 */
static void build_and_save(const char *filename)
{
struct json_object *root, *arr, *item;
FILE *fp;
root = json_object_new_object();
json_object_object_add(root, "device", json_object_new_string("ubuntu-gateway"));
json_object_object_add(root, "uptime_s", json_object_new_int(1200));
json_object_object_add(root, "online", json_object_new_boolean(1));
arr = json_object_new_array(); /* 数组:多个传感器 */
item = json_object_new_object();
json_object_object_add(item, "name", json_object_new_string("温度"));
json_object_object_add(item, "value", json_object_new_double(25.6));
json_object_object_add(item, "unit", json_object_new_string("C"));
json_object_array_add(arr, item);
item = json_object_new_object();
json_object_object_add(item, "name", json_object_new_string("湿度"));
json_object_object_add(item, "value", json_object_new_double(58.2));
json_object_object_add(item, "unit", json_object_new_string("%RH"));
json_object_array_add(arr, item);
json_object_object_add(root, "sensors", arr);
printf("① 生成的 JSON(整齐格式):\n%s\n",
json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
fp = fopen(filename, "w");
if (fp) {
fprintf(fp, "%s\n", json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
fclose(fp);
printf(" 已写入文件:%s\n", filename);
} else {
perror("fopen");
}
json_object_put(root); /* 释放(json-c 是引用计数) */
}
/* ② 解析一段 JSON 文本,取出里面的字段 */
static void parse_text(const char *text)
{
struct json_object *root, *v;
printf("\n② 解析这段 JSON:%s\n", text);
root = json_tokener_parse(text);
if (!root) {
printf(" 解析失败(格式不对)\n");
return;
}
if (json_object_object_get_ex(root, "cmd", &v))
printf(" cmd = %s\n", json_object_get_string(v));
if (json_object_object_get_ex(root, "interval", &v))
printf(" interval = %d\n", json_object_get_int(v));
if (json_object_object_get_ex(root, "enable", &v))
printf(" enable = %s\n", json_object_get_boolean(v) ? "true" : "false");
json_object_put(root);
}
/* ③ 从文件读 JSON 并解析 */
static void parse_file(const char *filename)
{
struct json_object *root, *v;
root = json_object_from_file(filename);
if (!root) {
printf("读文件失败:%s(先用 ./json_check.py make 生成它)\n", filename);
return;
}
printf("③ 解析文件 %s:\n%s\n", filename,
json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
if (json_object_object_get_ex(root, "interval", &v))
printf(" 取到 interval = %d(下一步可以拿它当采集间隔)\n", json_object_get_int(v));
json_object_put(root);
}
int main(int argc, char *argv[])
{
if (argc >= 3 && strcmp(argv[1], "read") == 0) {
parse_file(argv[2]);
return 0;
}
build_and_save("report.json");
parse_text("{\"cmd\":\"set\",\"interval\":7,\"enable\":true}");
printf("\n完成:报告写在 report.json,可用 python3 json_check.py check 校验\n");
return 0;
}
附录 C|文件 json_check.py
python
#!/usr/bin/env python3
"""课时 21 的"测试工具":用 Python 的标准库 json 生成/校验 JSON 文件
用法:
python3 json_check.py make # 生成 cmd.json(给 C 程序解析用)
python3 json_check.py check # 校验 C 程序生成的 report.json
"""
import json
import sys
MODE = sys.argv[1] if len(sys.argv) > 1 else "check"
if MODE == "make":
cmd = {"cmd": "set", "interval": 7, "enable": True, "target": "127.0.0.1"}
with open("cmd.json", "w", encoding="utf-8") as f:
json.dump(cmd, f, ensure_ascii=False, indent=2)
print("已生成 cmd.json:")
print(json.dumps(cmd, ensure_ascii=False, indent=2))
print("接下来执行: ./json_demo read cmd.json")
sys.exit(0)
# check:校验 C 程序生成的 report.json
try:
with open("report.json", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print("找不到 report.json:先运行 ./json_demo 生成它")
sys.exit(2)
except json.JSONDecodeError as e:
print(f"report.json 不是合法 JSON:{e}")
sys.exit(1)
ok = True
def check(cond, msg):
global ok
print((" [通过] " if cond else " [失败] ") + msg)
if not cond:
ok = False
print("校验 report.json:")
check(data.get("device") == "ubuntu-gateway", f"device = {data.get('device')}")
check(isinstance(data.get("uptime_s"), int), f"uptime_s = {data.get('uptime_s')}")
sensors = data.get("sensors")
check(isinstance(sensors, list) and len(sensors) == 2, f"sensors 是数组且有 {len(sensors) if isinstance(sensors, list) else '?'} 项")
if isinstance(sensors, list) and sensors:
for s in sensors:
check(isinstance(s.get("value"), (int, float)), f"传感器 {s.get('name')} 的 value = {s.get('value')} {s.get('unit')}")
print("全部通过 ✅" if ok else "有失败项 ❌")
sys.exit(0 if ok else 1)