65.9K
CodeProject 正在变化。 阅读更多。
Home

jWrite - C 中的一个非常简单的 JSON 编写器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (12投票s)

2015年3月18日

CPOL

5分钟阅读

viewsIcon

89911

downloadIcon

3378

一组简单的函数,用于轻松无误地将 C 变量输出到 JSON

引言

jWrite 是一种在 C 语言中直接从原生变量向 char 缓冲区写入 JSON 的简单方法。它会管理输出缓冲区,防止溢出,处理所有棘手的引号、括号和逗号,并报告您尝试创建无效 JSON 的位置。现在有了一个 C++ 版本,并附带了适用于 Arduino 的演示草图。

当然,您也可以使用 sprintf() 将 json 写入 string... 但 jWrite 要好得多。

背景

这是一组与“jRead - in-place JSON 元素读取器”相伴的函数 ( https://codeproject.org.cn/Articles/885389/jRead-an-in-place-JSON-element-reader )。

基本设计原则相同:它应该是纯 C 语言,内存开销很小或没有,执行速度快且易于使用。它旨在用于嵌入式项目,在这些项目中,使用一些大型的 C++ 结构化解决方案是不合适的。

考虑了多种方法;“最自动化”的方法是定义一个描述 JSON 的结构,其中包含指向外部变量的指针以获取数据——这似乎是个好主意,因为它只需要一次调用就可以“字符串化”所有内容……缺点是定义这种结构和将其全部保存在内存中所需的 API 复杂度。

对于程序员来说,让 JSON 写入器的配置比手动编写内容更复杂是没有意义的!

jWrite 试图取得一个折衷方案,即它很简单,而且“看起来”没有做什么——您只需告诉它要写入什么,它就会完成。

Using the Code

直接开始

jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( "key", "value" );                  // writes "key":"value"
jwObj_int( "int", 1 );                           // writes "int":1
jwObj_array( "anArray");                         // start "anArray": [...] 
    jwArr_int( 0 );                              // add a few integers to the array
    jwArr_int( 1 );
    jwArr_int( 2 );
jwEnd();                                         // end the array
err= jwClose();                                  // close root object - done

结果是

{
    "key": "value",
    "int": 1,
    "anArray": [
        0,
        1,
        2
    ]
}

输出是美化的(这是一个选项),并且所有 { } [ ] , : " 字符都放置在正确的位置。

虽然这看起来非常直接,与一大堆 sprintf() 并没有多大区别,您可能会说,但 jWrite 做了几件非常有用但您可能没注意到的事情:它帮助您生成有效的 JSON

您可以轻松地调用一系列无效的 jWrite 函数,例如

jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( "key", "value" );                  // writes "key":"value"
jwObj_int( "int", 1 );                           // writes "int":1
    jwArr_int( 0 );                              // add a few integers to the array
...

由于 JSON 根是一个对象,我们必须插入 "key":"value" 对,因此此时调用 jwArr_int( 0 ) 是无效的……这会将内部错误标志设置为“尝试将数组值写入对象”,并忽略后续的函数调用,直到结束的 jwClose() 调用时,它才会报告错误。

在编写大型 JSON 文件时,可能很难弄清楚您在哪里出错了……在这种情况下,jWrite 会通过提供导致错误的函数编号来提供帮助,并将部分构造的 JSON 保留在您的缓冲区中(带有“\0”终止符)。在上例中,jwErrorPos() 将返回 4,因为此序列中的第 4 个函数导致了错误(jwOpen() 调用编号为 1

由于 jWrite 处理 JSON 格式,因此很容易以编程方式(而不是像上面那样内联)创建输出,例如

jwOpen( buffer, buflen, JWOBJECT, JW_COMPACT );    // outer JSON is an object, compact format

jwObj_array( "myArray" );                          // contains an array: "myArray":[...]
for( i=0; i<myArrayLen; i++ )
    jwArr_int( myArray[i] );                       // write zero or more array entries
jwEnd();

err= jwClose();

在此示例中,myArrayLen 可以是任何值(0,1,2...),jWrite 会处理输出并正确放置数组值分隔符逗号。

可以使用 ObjectArrayintdoubleboolnullstring 值类型创建任何有效的 JSON 序列。您还可以通过原始插入来添加自己的字符串化值,例如: jwObj_raw( "key", rawtext ).

main.c 中有更长的示例,在 jWrite.h 中有一些更多信息。

C++“Arduino”版本

原始的 jWrite(纯 C 语言)已被转换为一个 C++ 类,可以在任何平台上使用。jWrite_Demo 下载包含一个 Arduino 草图,该草图在 Arduino IDE 的串行监视器上打印一些示例。

使用 C++ 版本与 C 版本非常相似,上面的第一个示例变成

jWrite jw( buffer, buflen );        // Create jWrite instance to use application buffer 
jw.open( JW_OBJECT, JW_PRETTY );    // open root node as object 
jw.obj_string( "key", "value" );    // writes "key":"value" 
jw.obj_int( "int", 1 );             // writes "int":1 
jw.obj_array( "anArray");           // start "anArray": [...] 
   jw.arr_int( 0 );                 // add a few integers to the array 
   jw.arr_int( 1 ); 
   jw.arr_int( 2 ); 
jw.end();                           // end the array 
err= jw.close();                    // close root object - done

jWrite_Demo.ino 草图显示了几个更长的示例。

值得关注的点(C 版本)

内部控制结构

在内部,jWrite 函数维护一个对象/数组深度的堆栈,并在每次调用时检查是否会导致错误。一个几乎不值一提但很重要的点是,它会管理您的输出缓冲区——一旦您将缓冲区和长度传递给 jwOpen(),它就不会溢出(它会返回“输出缓冲区已满”错误),并且它会保持“\0”终止。

您可能已经意识到,这些函数必须将一些状态信息(和节点堆栈)存储在某个地方……

……是的,有一个 struct jWriteControl 结构,它会跟踪内部状态并被所有函数使用。

有些人可能会说“哦,好吧,很好”,而有些人可能会说“等等……那不是全局的,对吧?”

嗯,是的,也否……

全局,还是不全局

对于许多应用程序来说,拥有一个用于 jWrite 的全局(static)结构实例要简单得多,它可以使 API 调用更容易输入——您不必每次都提供引用。

但是,这不够灵活,不允许同时多次使用 jWrite 函数,因此 jWrite 允许您通过取消定义 JW_GLOBAL_CONTROL_STRUCT 来关闭全局。这会导致所有 API 函数都需要一个指向应用程序提供的 struct jWriteControl 实例的指针。

上面带有注释掉的 #define JW_GLOBAL_CONTROL_STRUCT 的示例看起来像

struct jWriteControl jwc;
jwOpen( &jwc, buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( &jwc, "key", "value" );                  // writes "key":"value"
jwObj_int( &jwc, "int", 1 );                           // writes "int":1
jwObj_array( &jwc, "anArray");                         // start "anArray": [...] 
    jwArr_int( &jwc, 0 );                              // add a few integers to the array
    jwArr_int( &jwc, 1 );
    jwArr_int( &jwc, 2 );
jwEnd( &jwc );                                         // end the array
err= jwClose( &jwc );                                  // close root object - done

这需要输入更多内容,并且很自然地会让人想到一个 C++ 类……现在已经编写了一个 C++ 类,并附带了一个 Arduino 的示例草图,尽管该类本身适用于任何平台。

结论

jWrite 和 jRead 使用简单,可以在没有开销或复杂 API 的情况下在 C 语言中处理 JSON,特别是对于仍然需要注意内存和处理器使用的嵌入式项目而言。

下载的 jWrite_1v2.zip 包含 jWrite.c/jWrite.h 的源代码以及一个运行几个示例的 Windows 命令行 main.c。在 VS2010 中使用附带的项目文件进行编译。

下载的 jWrite_Demo 包含 jWrite.cpp/jWrite.hpp 的源代码以及 jWrite_Demo.ino 草图,该草图运行几个示例并将结果打印到 Arduino IDE 的串行监视器。它可以在 Arduino UNO 及以上版本上运行。jWrite 类通常适用于任何平台。

jWrite C 版本使用 C89 编写,没有任何依赖项。

© . All rights reserved.