jWrite - 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 会处理输出并正确放置数组值分隔符逗号。
可以使用 Object
、Array
、int
、double
、bool
、null
和 string
值类型创建任何有效的 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 编写,没有任何依赖项。