专栏首页单片机爱好者ESP8266_16天气预报之JSON数据的生成与解析

ESP8266_16天气预报之JSON数据的生成与解析

上一节通过HTTP的GET操作,我们获取了天气相关的信息。但相关信息是以JSON格式发下来的,所以需要对数据进行解析。解析之前先明确几点:

1、JSON是一种数据格式,不是通信协议。有专门的解析函数,但如果懒得用,可以用特定的方法获取想要的数据。

2、解析数据的前提是了解数据的结构,而不是说对一组结构陌生的数据进行解析。

以上一节得到的数据为例:

{"results":[{"location":{"id":"WX4BZD6KEJFY","name":"Langfang","country":"CN","path":"Langfang,Langfang,Hebei,China","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"Overcast","code":"9","temperature":"20"},"last_update":"2019-09-19T20:15:00+08:00"}]} 

这样看有点乱,整理一下:

{
"results": [{
"location": {
"id": "WX4BZD6KEJFY",
"name": "Langfang",
"country": "CN",
"path": "Langfang,Langfang,Hebei,China",
"timezone": "Asia/Shanghai",
"timezone_offset": "+08:00"
},
"now": {
"text": "Overcast",
"code": "9",
"temperature": "20"
},
"last_update": "2019-09-19T20:15:00+08:00"
}]
}

这样是不是整齐多了?

你会发现,这组数据貌似有个名字:results,它由三部分组成:location,now和last_update。也就是位置、当前天气和最后更新时间。剩下就是一些标点,可以最后考虑。

以“"id": "WX4BZD6KEJFY"”为例,前面的是“键”,后面的是“值”,合在一起,叫键值对。

可以看到,温度是27度。但程序没有眼睛,不能直接看,怎么办呢?偷懒的方式:从第一个符号“{”出现,计算数字“27”出现的位置,只要你的API链接不变,网站提供的数据格式不更新,一般来说也能行。

但这个方法有点low,所以,还是用JSON函数解析吧。

首先说一下,官方的SDK里面(例程IoT_Demo)提供了JSON相关的库,可以直接使用,所以这里的讲解主要参照官方,顺便百度了一下,如果有说的不对的地方,欢迎指正。

代码不少,但重复的比较多,所以理解起来不是很难。大致分6步:

1

添加头文件和相关源文件

这个简单,主要是user_json.c和user_json.h,放到指定位置。不会的,看我工程文件。

2

更新JSON数据

第二步就能获取数据了?

其实不是,因为数据的解析是一个整体的操作,这只是其中的一个环节。先看一下SDK中的例程文件user_webserver.c:

LOCAL int ICACHE_FLASH_ATTR
light_status_get(struct jsontree_context *js_ctx)
{
    const char *path = jsontree_path_name(js_ctx, js_ctx->depth - 1);
    if (os_strncmp(path, "red", 3) == 0) {
        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_RED));
    } else if (os_strncmp(path, "green", 5) == 0) {
        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_GREEN));
    } else if (os_strncmp(path, "blue", 4) == 0) {
        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_BLUE));
    }
..........
    return 0;
}

因为篇幅原因,去掉了一些代码。从函数名字可以看出来,它有一个关键词“get”,也就是“获取”。所以,这个函数的功能是把解析好的数据更新到JSON结构里。

明白了函数的功能,先定义一个“location”的更新函数,代码如下:

int ICACHE_FLASH_ATTR
json_location_get(struct jsontree_context *js_ctx)
{
    const char *path = jsontree_path_name(js_ctx, js_ctx->depth - 1);
    if (os_strncmp(path, "id", os_strlen("id")) == 0) {
     jsontree_write_string(js_ctx, "WX4BZD6KEJFY");
    }
    else if (os_strncmp(path, "name", os_strlen("name")) == 0) {
     jsontree_write_string(js_ctx, "Langfang");
    }
    else if (os_strncmp(path, "country", os_strlen("country")) == 0) {
     jsontree_write_string(js_ctx, "CN");
    }
    else if (os_strncmp(path, "path", os_strlen("path")) == 0) {
     jsontree_write_string(js_ctx, "Langfang,Langfang,Hebei,China");
    }
    else if (os_strncmp(path, "timezone", os_strlen("timezone")) == 0) {
         jsontree_write_string(js_ctx, "Asia/Shanghai");
    }
    else if (os_strncmp(path, "timezone_offset", os_strlen("timezone_offset")) == 0) {
         jsontree_write_string(js_ctx, "+08:00");
    }
    return 0;
}

同样的结构,同样的代码,就是把信息换一下。对比上下两个函数中的两行代码:

jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_RED));
jsontree_write_string(js_ctx, "WX4BZD6KEJFY");

第一行,因为要写入整形数据,所以使用函数“jsontree_write_int”。

第二行,因为要写入字符串,所以使用函数“jsontree_write_string”。

还有一点注意事项,第二行写入的数据是死的,即便后面更新了数据,新的数据也进不来。这里写成这样的方式是为了演示,理解方便。

同样的方式,创建now和last_update这两个类型。

3

创建解析函数

参照SDK中的例程文件user_webserver.c(篇幅原因,去掉一些代码):

LOCAL int ICACHE_FLASH_ATTR
light_status_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
    while ((type = jsonparse_next(parser)) != 0) {
        if (type == JSON_TYPE_PAIR_NAME) {
            if (jsonparse_strcmp_value(parser, "red") == 0) {
                uint32 status;
                jsonparse_next(parser);
                jsonparse_next(parser);
                status = jsonparse_get_value_as_int(parser);
                r=status;
                os_printf("R: %d \n",status);
            } else if (jsonparse_strcmp_value(parser, "green") == 0) {
                uint32 status;
                jsonparse_next(parser);
                jsonparse_next(parser);
                status = jsonparse_get_value_as_int(parser);
                g=status;
                os_printf("G: %d \n",status);
            }
...................
        }
    }
    return 0;
}

第2步的函数更新数据,那么数据从哪来?就是这里函数提供的。

程序中会分别判断red对应的值和green对应的值。如果有需要,可以进行相应的操作。不难理解吧?修改一下,换成我们需要的函数(篇幅有限,依然省略部分):

int ICACHE_FLASH_ATTR
json_location_parse(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
int type;
char buffer[64];
while ((type = jsonparse_next(parser)) != 0){
if (type == JSON_TYPE_PAIR_NAME){
if (jsonparse_strcmp_value(parser, "id") == 0){
jsonparse_next(parser);
jsonparse_next(parser);
jsonparse_copy_value(parser, buffer, sizeof(buffer));
os_printf("id is = %s\r\n", buffer);
}
else if(jsonparse_strcmp_value(parser, "name") == 0){
jsonparse_next(parser);
jsonparse_next(parser);
jsonparse_copy_value(parser, buffer, sizeof(buffer));
os_printf("name is = %s\r\n", buffer);
}
else if(jsonparse_strcmp_value(parser, "country") == 0){
jsonparse_next(parser);
jsonparse_next(parser);
jsonparse_copy_value(parser, buffer, sizeof(buffer));
os_printf("country is = %s\r\n", buffer);
}
.............................
}
}
return 0;
}

因为咱们获取的是字符串信息,所以这里使用了函数“jsonparse_copy_value”。同理,创建now和last_updata的解析函数。

4

注册更新JSON数据和解析JSON数据的回调函数

参照SDK中的例程文件user_webserver.c中的代码,第394行:

LOCAL struct jsontree_callback light_callback =
JSONTREE_CALLBACK(light_status_get, light_status_set);

light_status_get是生成,light_status_set是解析。回调函数以结构体的形式注册,light_callback是结构体的名字。

所以,参照这个格式往里套,location的代码如下:

LOCAL struct jsontree_callback json_location_Callback =
JSONTREE_CALLBACK(json_location_get, json_location_parse);

now和last_updata同理:

LOCAL struct jsontree_callback json_now_Callback =
JSONTREE_CALLBACK(json_now_get,json_now_parse);
LOCAL struct jsontree_callback json_last_update_Callback =
JSONTREE_CALLBACK(json_last_update_get, json_last_update_parse);

5

生成JSON数的对象

参照SDK中的例程文件user_webserver.c中的代码,第397行:

JSONTREE_OBJECT(rgb_tree,
                JSONTREE_PAIR("red", &light_callback),
                JSONTREE_PAIR("green", &light_callback),
                JSONTREE_PAIR("blue", &light_callback),
                JSONTREE_PAIR("cwhite", &light_callback),
                JSONTREE_PAIR("wwhite", &light_callback),
                );

所以,先生成一个location的对象:

JSONTREE_OBJECT(location,
                JSONTREE_PAIR("id", &json_location_Callback),
                JSONTREE_PAIR("name", &json_location_Callback),
                JSONTREE_PAIR("country", &json_location_Callback),
                JSONTREE_PAIR("path", &json_location_Callback),
                JSONTREE_PAIR("timezone", &json_location_Callback),
                JSONTREE_PAIR("timezone_offset", &json_location_Callback)
);

然后,依次生成now和last_updata这两个对象。

现在分别生成了location、now和last_updata这三个结构,要把这三个结构组合起来:

JSONTREE_OBJECT(resultsArray,
JSONTREE_PAIR("location",&location),
JSONTREE_PAIR("now", &now),
JSONTREE_PAIR("last_update", &json_last_update_Callback)
);

得到的是这样一个结构:

{
"location": {
"id": "WX4BZD6KEJFY",
"name": "Langfang",
"country": "CN",
"path": "Langfang,Langfang,Hebei,China",
"timezone": "Asia/Shanghai",
"timezone_offset": "+08:00"
},
"now": {
"text": "Overcast",
"code": "9",
"temperature": "20"
},
"last_update": "2019-09-19T20:15:00+08:00"
}

然后,加上方括号:

JSONTREE_ARRAY(jsonArray,JSONTREE_PAIR_ARRAY(&resultsArray));

再然后,生成名为“results”的对象:

JSONTREE_OBJECT(object,
JSONTREE_PAIR("results", &jsonArray),
);

至此,一个完整的数据结构已经创建好了,跟天气网站发给我们的结构是一样的。

再再然后,我们需要把这个结构放进一个对象里,方便调用:

JSONTREE_OBJECT(temp,
JSONTREE_PAIR("object", &object),
);

6

解析天气数据

参照SDK中的例程文件user_webserver.c中的代码,第1606行:

jsontree_setup(&js, (struct jsontree_value *)&wifi_req_tree, json_putchar);
json_parse(&js, pParseBuffer);

改为我们的代码:

struct jsontree_context js;
jsontree_setup(&js, (struct jsontree_value *)&object, json_putchar);
json_parse(&js,c);

函数“jsontree_setup”生成格式数据树,函数“json_parse”对数据进行解析,并打印出来。其中,“c”是指针,指向从“{”开始的JSON数据。具体可以看我工程里的代码,这里就不再贴出来了。

程序修改完成,保存、清理、编译、下载一条龙,然后重新上电。这里借助串口助手来查看效果。效果如下所示:

从截图上可以看到,程序通过解析函数,已经成功获取到了JSON结构中的所有信息,接下来,你就可以把天气信息显示在某些设备上了。

链接:

https://pan.baidu.com/s/1BOUrZWAGJxMjQAyBtxhyuQ

提取码:gozo

本文分享自微信公众号 - 单片机爱好者(mcucoder),作者:MCU启航

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 指针在液晶屏显示中的用法(二)

    本文纯属虚构,如有雷同,基本不可能。 由于版式原因,如果要看代码部分,建议电脑端观看。

    MCU起航
  • 地心一号-超迷你自平衡小车

    大家好,我是起航,这次跟大家聊聊平衡小车。了解我的朋友都知道,我极有可能会把帖子写的又臭又长,所以,,,做好准备,上车吧!

    MCU起航
  • 闲聊:关于能量反馈型电子负载的一些问题

    关于文章的发布方向,虽然是单片机为主,但也尽量考虑一些其它的类型。毕竟固步自封,没什么好的结果。也欢迎大家留言,把你们感兴趣的话题说出来。太简单的,像查个数据...

    MCU起航
  • 运筹优化的就业前景,你了解多少?

    学习运筹优化的童鞋们在被各种算法代码虐了无数遍后,发出疑问? 学……学它有前途吗?

    用户1621951
  • 深度解析云计算的12个顶级安全威胁

    导读: 越来越多企业数据和应用程序正在转向于云计算,这造成了云上面临更多的安全挑战。以下是使用云服务时,所要面对的12个顶级安全威胁。 云计算不断改变企业在使用...

    企鹅号小编
  • 亚马逊无人超市Amazon Go这次是真的真的开业了

    安妮 编译整理 量子位 出品 | 公众号 QbitAI 来了来了。 经过14个月的尝试摸索,位于西雅图的亚马逊首家无人商店Amazon Go即将在当地时间1月2...

    量子位
  • 数据泵导出导入物化视图(ORA-39083)

    有网友问,物化视图是否能单独进行导出和导入呢?因为导出不报错,但是导入的时候报错了,报错信息如下所示:

    小麦苗DBA宝典
  • 聊聊dubbo-go的ConsistentHashLoadBalance

    本文主要研究一下dubbo-go的ConsistentHashLoadBalance

    codecraft
  • 聊聊dubbo-go的ConsistentHashLoadBalance

    本文主要研究一下dubbo-go的ConsistentHashLoadBalance

    codecraft
  • input file文件上传(multiple)及FileReader:读取本地图片文件并显示

    要想在页面上显示本地图片,以前我们通常的做法是将选择的图片文件上传至后端服务器,后端对其进行存储,再将图片的URL返回到前端,前端通过这个URL来显示图片。而H...

    山河木马

扫码关注云+社区

领取腾讯云代金券