21 嵌入式操作系统 | JSON 与 json-c:生成与解析

嵌入式操作系统 | 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 标准库反向校验,配套课堂练习与课后作业。

本节目标

  1. 能看懂 JSON 的结构(对象、数组、键值对),知道它比自定义文本好在哪里
  2. 会用 json-c 生成 JSON(对象/数组/字符串/数字/布尔)并写入文件
  3. 会用 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)
相关推荐
j7~1 小时前
【Python】(篇六)《基础语法(函数、列表和元组、字典、文件)》---详解
开发语言·python·文件·函数·字典·编程学习·元组与列表
重生之小比特1 小时前
【C++进阶】红黑树的实现
java·开发语言·c++
纪念 2291 小时前
C++算法(一)
开发语言·c++·算法
Yunovian3 小时前
AI时代,针对模型与应用,浅谈一下各编程语言
开发语言·c++·人工智能·python·ai·rust·ai编程
周杰伦fans4 小时前
构建真正智能的CAD AI绘图代理
开发语言·人工智能·c#
心中有你02144 小时前
C语言埃氏筛法(素数筛选)
c语言·数据结构·算法
zhanghaha13146 小时前
Python进阶教程:35_PyQt (保姆级零基础教程)
开发语言·python·pyqt
释厄6236 小时前
多黑洞具现论·物理/生物/金融三域显化·虚拟宇宙黑洞·奇点=0 圆圈
开发语言·数据结构·人工智能·程序人生·安全威胁分析
Fruit_Caller8 小时前
static
开发语言·c++