json解析库jansson的简单使用 基于stm32

#适用于STM32F103芯片
#使用stm32标准库
#文档等更多参考
https://www.oschina.net/p/jansson
https://blog.csdn.net/weixin_44177946/article/details/105910870

之前在做openmv与单片机通讯时,遇到了json的解析问题,根据openmv中文平台的指导选择了jansson库——这是一个C语言的json解析库,可以将包含json的文本解析成为C的数据格式。
作为初学者的我,在接触到这个库的时候其实完全是懵的,而openmv官方给出的教程以及csdn上的教程又比较少,大多数时候只有代码,解释相对较少。在付出了一些努力学会一部分并写了一些使用函数的我,想把我写的这个简单到以至于“简陋”的函数发布到博客中,并且更加详细地解释jansson的函数使用。
我使用了jansson库的一系列函数,实现了包括串口初始化,从串口读取文本,解析文本并存为jansson的数据结构,解析等功能,包含了必须的json元素的类型判断,提取整型变量,提取字符串,提取浮点型变量等函数。这些在jansson之上写的函数保证了安全性,并且大大简化了openmv与单片机通讯时json的解析时的步骤。
接下来我通过解释自己写的内容并结合文档的翻译来解释jansson库里函数的作用,力求精确而易懂——当然,作为一个初学者,知识难免会有疏漏,希望各位读者能够指出。

希望解析json,首先需要获取json字符串,由于使用的是单片机,自然会使用串口,于是,在代码中需要加入串口的初始化函数并且在中断服务函数中接收字符串。这里相对比较简单,代码就不放出来了。
值得注意的是,需要在这里约定好结束的字符,即在json传输末尾加入这个结束符用于表示结束,否则接收字符串会出现错误,我使用的是’/r’。
接收字符串被保存在一个全局字符数组中,全局变量在json.c中被定义,并且使用static修饰,用于保证存储的数据不会被破坏。为了降低接收字符串可能在解析的时候被修改,从而解析失败的可能性,我使用了一个专用的解析字符串,只有在接收到终止标志位的时候才会被修改。两个字符串的大小均由宏定义json_buffer_size决定。
如果希望解析缓存区(即解析字符串中)的json字符串,可以调用我写的JsonLoadData()函数,该函数调用了jansson的解析函数json_loads(),并且如果发生错误则返回非零值,可以在宏定义中寻找对应出错原因,方便定位错误原因。
json_loads()函数是jansson提供的解析json文本的函数,它接收一个字符串,一个整型,以及一个记录错误的数据结构,返回一个指向json_t(jansson响应的数据结构)指针。
在jansson官方文档中是这样表述json_loads()的:
/*****************************************************************************
json_t *json_loads(const char *input, size_t flags, json_error_t *error)
Return value: New reference.
Decodes the JSON string input and returns the array or object it contains, or NULL on error, in which case error is filled with information about the error. flags is described above.
返回值:新的json_t引用(指向json_t的指针)
描述:解析输入json字符串并返回其包含的数组或对象,或者在错误时为NULL,在该情况下,error被赋予一个包含错误的信息(error为一个结构体)。参数flags在上文被描述。

*******************************************************************************/
json_loads()的flags参数我设置为0,一般来说不用使用,error也并没有使用。
到这里读者可能就觉得既然已经解析出来了,那就万事大吉了,已经可以使用这个库了。但是读者可就大错特错了,这仅仅是万里长征的第一步。
C语言自身不支持这种数据类型!!!
我们看看官方文档怎么说的,
The JSON specification (RFC 4627) defines the following data types: object, array, string, number, boolean, and null. JSON types are used dynamically; arrays and objects can hold any other data type, including themselves. For this reason, Jansson’s type system is also dynamic in nature. There’s one C type to represent all JSON values, and this structure knows the type of the JSON value it holds.
“Jansson’s type system is also dynamic in nature.”意味着你不可以直接读取解析的json_t*对应的值,你需要通过它提供的api去查找这些数据,并且返回!
在这里,我可以很合理的猜测load返回值是一个指向链表的指针,而json_t就是一个结构体——虽然我并没有证据。不过这可以很好的解释jansson为什么可以嵌套自身以及动态存储数据,以及,为什么要讲stm32的堆空间设置的大一些。
因此,我们需要一些步骤来获取数据。这里以我写的JsonGetInt为例

int JsonTypeGet(json_t *element)
{
    switch (json_typeof(element)) 
    {
        case JSON_OBJECT:
            return json_type_object;
        case JSON_ARRAY:
            return json_type_array;
        case JSON_STRING:
            return json_type_string;
        case JSON_INTEGER:
            return json_type_integer;
        case JSON_REAL:
            return json_type_real;
        case JSON_TRUE:
            return json_type_true;
        case JSON_FALSE:
            return json_type_false;
        case JSON_NULL:
            return json_type_null;
        default:
            return json_get_type_error;
    }
}

// if index >= 0, you can get int data from array[index]
int JsonGetInt(char *key, int *int_receive, int index)
{
    json_t *type_obj, *array_item;
    if (JsonTypeGet(root) != json_get_type_error)
    {
        type_obj = json_object_get(root, key);
        if (!type_obj){json_decref(type_obj);return json_get_object_error;}
        else
        {
            if (index < 0)
            {
                if (JsonTypeGet(type_obj) != json_type_integer)
				{
					json_decref(type_obj);
					return json_type_error;
				}
                else
                {
                    *int_receive = json_integer_value(type_obj);
					json_decref(type_obj);
					return json_no_error;
                }
            }
            else
            {
                if (JsonTypeGet(type_obj) != json_type_array)
				{
					json_decref(type_obj);
					return json_type_error;
				}
                else
                {
                    array_item = json_array_get(type_obj, index);
                    if (JsonTypeGet(type_obj) != json_type_integer)
					{
						json_decref(type_obj);
						return json_type_error;
					}
                    else
                    {
                        *int_receive = json_integer_value(array_item);
						json_decref(type_obj);
						return json_no_error;
                    }
                }
            }
        }
    }
    else return json_get_type_error;
}

如果需要解析int类型数据,需要使用json_object_get通过key对数据进行获取,然后使用json_integer_value返回整型数据。
json_object_get是返回一个json_t类型的数据,储存着通过key获取的值。
官方文档是这样解释的:
/*****************************************************************************
json_t *json_object_get(const json_t *object, const char *key)
Return value: Borrowed reference.
Get a value corresponding to key from object. Returns NULL if key is not found and on error.
返回值:对存储数据的json_t引用(按照我的理解)
从json对象获取一个与键值相对应的值。返回NULL如果没有相应的key或者发生错误

*******************************************************************************/
json_interger_value自然是讲json_t储存的整型数据读取出来并返回,这个比较简单,就不详细展开了。
为了保证方便寻找解析出错的原因,我在函数中加入JsonTypeGet进行类型判断以及各个步骤是否成功的判断。同时,为了读取方便,我加入了对于数组读取的支持,index<0则解析数字,>0则解析数组。这个比较简单,留给读者自己阅读理解。
json_typeof用于返回json_t的类型,通过switch…case进行判断,并返回一个整数,如json_type_object等。这些在json.h中进行定义。

总的来说,jansson库解析json一般分为几个步骤:

  1. 获取json字符串
  2. 使用json_loads将数据载入jansson的数据结构中
  3. 使用json_object_get通过键值(key)获取储存着相应值的json_t指针(应该为结构体指针)
  4. 使用json_integer_valuejson_real_valuejson_string_value等函数对数据进行读取

另外,使用json库需要注意内存管理,json_t*不使用后需要调用json_decref(),这个有点像引用计数的内存管理机制,每次调用decref都会造成json_t*对应的计数减1,当计数到达0时,销毁json_t*对应的数据。
不注意内存管理可能造成内存泄漏,而且我认为可能会造成解析json失败。
以上就是jansson库的使用问题,针对stm32,其实jansson还有一个比较大的问题需要解决。
这里我使用了正点原子的usart.c,但是这个其实是与jansson不太兼容的,原因似乎是正点原子重新定义了fputc以适应串口输出。所以在使用jansson库的时候无法使用usart.c。
这是一个比较大的问题,因为输出一些调试数据需要使用printf到串口上,如果没法用这个文件,则无法输出调试数据。
我的解决方法就是不适用stdio.h中的printf,并且在gitee/github上找到了xprint这个库,用来解决输出的问题。
需要xprint输出数据,需要xdev_out设置一个回调函数。
xdev_out仅需要传入一个函数指针,这个函数指针用于指示xprint如何输出数据。实际上,printf输出以及scanf输入都是基于单个字符的,也就是说,只要定义了如何接收单个字符,便定义了如何发送和接收字符串。
对于串口输出数据来说,即Usart_SendData(Usart, chr(char类型))。但是xdev_out的函数指针要求函数只可以带一个参数,所以需要用另外一个函数包装一下,这个包装的函数只有一个参数,无返回值。USE_USART自行输入需要输出的串口。

void send_data(char chr)
{
	USART_SendData(USE_USART, chr);
	delay_us(100);
}

这里为什么要延时100us呢,因为实测不延时无法接收到正确的数据。
接下来调用xdev_out(send_data),整个xprintf就可以使用了。
这个大概就是jansson库使用的一个比较简单的教程,希望可以对各位有用。
整个代码我会放到gitee上供各位下载,以下是链接:
https://gitee.com/mingerfan/jansson-is-easy-to-use
源码在HARDWARE/json中

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值