拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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 都支持),所以设备上报、前后端交互都用它。

本课要生成的数据长这样:

{"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 库)

cd~/lab/课时21-JSON与json-c gcc-Wall-Wextra-ojson_demo json_demo.c -ljson-c

现象:无输出即成功。json-c 是课时 15 用apt install libjson-c-dev装上的(库在系统目录,不用自己编)。

第 2 步:先用 Python 造一个 JSON 文件给 C 程序解析(终端 A)

python3 json_check.pymakecatcmd.json

现象:

已生成 cmd.json: { "cmd": "set", "interval": 7, "enable": true, "target": "127.0.0.1" }

第 3 步:C 程序解析这个文件(终端 B)

./json_demoreadcmd.json

现象:

③ 解析文件 cmd.json: { "cmd":"set", "interval":7, "enable":true, "target":"127.0.0.1" } 取到 interval = 7(下一步可以拿它当采集间隔)

第 4 步:C 程序生成 JSON 并写文件

./json_demo

现象(① 生成、② 解析内嵌字符串,最后提示文件已写):

① 生成的 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(换一种语言读它)

python3 json_check.py check

现象(Python 的 json 标准库读进来,逐项核对字段):

校验 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 校验报不是合法 JSONC 程序写文件时没写完就退出检查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.jsonpython3 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.jsonpython3 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|怎么运行(注意先后顺序)

gcc-Wall-Wextra-ojson_demo json_demo.c -ljson-c python3 json_check.pymake# ① 生成 cmd.json./json_demoreadcmd.json# ② C 解析这个文件./json_demo# ③ C 生成 report.json + 解析内嵌 JSONpython3 json_check.py check# ④ Python 校验 report.json

附录 B|文件json_demo.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 造一份多传感器上报数据,打印出来并存成文件 */staticvoidbuild_and_save(constchar*filename){structjson_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 文本,取出里面的字段 */staticvoidparse_text(constchar*text){structjson_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 并解析 */staticvoidparse_file(constchar*filename){structjson_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);}intmain(intargc,char*argv[]){if(argc>=3&&strcmp(argv[1],"read")==0){parse_file(argv[2]);return0;}build_and_save("report.json");parse_text("{\"cmd\":\"set\",\"interval\":7,\"enable\":true}");printf("\n完成:报告写在 report.json,可用 python3 json_check.py check 校验\n");return0;}

附录 C|文件json_check.py

#!/usr/bin/env python3"""课时 21 的"测试工具":用 Python 的标准库 json 生成/校验 JSON 文件 用法: python3 json_check.py make # 生成 cmd.json(给 C 程序解析用) python3 json_check.py check # 校验 C 程序生成的 report.json """importjsonimportsys MODE=sys.argv[1]iflen(sys.argv)>1else"check"ifMODE=="make":cmd={"cmd":"set","interval":7,"enable":True,"target":"127.0.0.1"}withopen("cmd.json","w",encoding="utf-8")asf: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.jsontry:withopen("report.json",encoding="utf-8")asf:data=json.load(f)exceptFileNotFoundError:print("找不到 report.json:先运行 ./json_demo 生成它")sys.exit(2)exceptjson.JSONDecodeErrorase:print(f"report.json 不是合法 JSON:{e}")sys.exit(1)ok=Truedefcheck(cond,msg):globalokprint((" [通过] "ifcondelse" [失败] ")+msg)ifnotcond:ok=Falseprint("校验 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)andlen(sensors)==2,f"sensors 是数组且有{len(sensors)ifisinstance(sensors,list)else'?'}项")ifisinstance(sensors,list)andsensors:forsinsensors:check(isinstance(s.get("value"),(int,float)),f"传感器{s.get('name')}的 value ={s.get('value')}{s.get('unit')}")print("全部通过 ✅"ifokelse"有失败项 ❌")sys.exit(0ifokelse1)
返回列表