使用 C++ 访问 JSON 数据






3.71/5 (9投票s)
本文档介绍了如何使用 JsonCpp 工具集读取、处理和写入 JSON 数据。
引言
在现代 Web 开发中,JSON (JavaScript Object Notation) 已取代 XML (Extensible Markup Language) 成为最流行的结构化数据格式。Web 应用程序通常依赖 JavaScript 来处理 JSON 数据,但桌面应用程序也可能需要读取和写入 JSON 数据。在这些情况下,了解如何使用 C++ 访问 JSON 格式的数据会很有帮助。
从头编写 JSON 解析器并不困难,但已经有一些解决方案可供使用。一个网站比较了三个工具集:JsonCpp、Casablanca 和 JSON Spirit。本文仅关注 JsonCpp。
确切地说,本文档介绍了如何使用 JsonCpp 解析 JSON 数据、处理数据以及将数据写入字符串或输出流。但在此之前,了解如何获取和构建该工具集非常重要。
1. 获取和构建 JsonCpp
Baptiste Lepilleur 已将 JsonCpp 发布为公共领域,但在使用它之前,您需要下载并构建源代码。代码可在 GitHub 上找到,您可以使用 Git 克隆存储库或通过单击绿色的 **Clone or Download** 按钮下载存档。
要构建 JsonCpp,您需要安装 CMake 构建系统。然后,您可以按照四个步骤构建库:
- 切换到包含 JsonCpp 源代码的目录,并创建一个目录来存放构建文件:
mkdir -p build/debug
- 切换到新目录:
cd build/debug
- 运行 CMake:
cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON \
-DBUILD_SHARED_LIBS=OFF -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
- 构建工具集:
make
如果此过程成功完成,build/debug 文件夹将在其 src/lib_json 文件夹中包含一个静态库 libjsoncpp.a。此外,顶层 JsonCpp 文件夹包含一个名为 include 的目录,其中包含 json/json.h 和 json/writer.h 等头文件。这些是编译访问 JsonCpp 例程的 C++ 应用程序所必需的。
可以通过设置 cmake
命令的参数来配置构建。例如,CMAKE_BUILD_TYPE
可以设置为 None
、Debug
、Release
、RelWithDebInfo
、MinSizeRel
或 Coverage
。另外,如果 BUILD_SHARED_LIBS
设置为 ON
,构建将在 build/debug/src/lib_json 文件夹中生成一个共享库。本文中的示例代码假定静态库 libjsoncpp.a 可用。
2. 概述
JsonCpp 工具集包含属于 Json
命名空间下的类。本文将重点介绍其中的五个类,表 1 列出了它们的名称及其用途:
表 1:JsonCpp 包中的重要类
类 | 目的 |
---|---|
Json::Reader |
读取和解析 JSON 数据 |
Json::Value |
存储 JSON 数据并使其可访问 |
Json::FastWriter |
将 JSON 数据写入单行字符串 |
Json::StyledWriter |
以人类可读的格式写入 JSON 数据 |
Json::StyledStreamWriter |
将 JSON 数据写入输出流 |
本文将按给定的顺序讨论这些类。下一节将介绍如何使用 Json::Reader
解析 JSON 数据。接下来的部分将介绍如何使用 Json::Value
处理 JSON 数据。再后面的部分将解释如何使用 Json::FastWriter
、Json::StyledWriter
和 Json::StyledStreamWriter
类写入 JSON 数据。
3. 解析 JSON 数据
Json::Reader
类中的核心函数是 parse
。有三个重载函数:
parse(const std::string& document, Json::Value& root, bool collectComments = true)
parse(const char* start, const char* end, Json::Value& root,
bool collectComments = true)
parse(std_istream& is, Json::Value& root, bool collectComments = true)
parse
的目标是将文本转换为 Json::Value
,这是 JSON 对象的 C++ 表示形式。三个函数之间的区别在于文本的来源。第一个函数从字符串读取文本,第二个函数在内存范围内读取字符数据,第三个函数从输入流读取文本。
这些函数都返回一个 bool
值,用于标识文本是否成功解析。如果解析成功,root
参数将包含有效的 JSON 数据。如果最后一个参数设置为 true,JSON 数据将包含注释。
除了 parse
之外,Json::Reader
类还提供了处理解析错误的功能。getFormattedErrorMessages
返回一个字符串,用于标识在解析过程中检测到的错误。以下代码演示了如何使用 Json::Reader
进行解析和错误检测。
Json::Reader reader; Json::Value root; std::string text = "{ \"first\": 1; \"second\": 2}"; if(!reader.parse(text, root)) { std::cout << reader.getFormattedErrorMessages() << std::endl; }
在这种情况下,parse
返回 false
,因为两个键值对由分号而不是逗号分隔。因此,getFormattedErrorMessages
返回以下内容:
* Line 1, Column 13 Missing ',' or '}' in object declaration
与 JavaScript 不同,JsonCpp 要求属性名(first
、second
)用双引号括起来。如果属性名未加引号,parse
将返回 false。
4. 处理 JSON 数据
如果 parse
成功完成,就可以通过 Json::Value
对象访问 JSON 数据。这使得可以使用 C++ 映射(map)表示法访问 JSON 属性。例如,在前述代码中,属性名为 first
,其值为 1
。如果 root
是 Json::Value
的名称,则 root["first"]
将返回 1
。同样,root["second"]
将返回 2
。请注意,通过映射返回的值类型为 Json::Value
。换句话说,Json::Value
可以看作是其他 Json::Value
的映射。
可以通过调用 type()
来获取值的类型。它返回 ValueType
枚举类型的值,该值可以是 nullValue
、intValue
、uintValue
、realValue
、stringValue
、booleanValue
、arrayValue
或 objectValue
。例如,root["second"].type()
返回 Json::uintValue
。
表 2 列出了 type
和 Json::Value
类的其他九个函数。
表 2:Json::Value 的函数
函数 | 描述 |
---|---|
type() |
标识值数据的类型 |
size() |
提供值中包含的值的数量 |
empty() |
如果值不包含任何值,则返回 true |
clear() |
删除值中的所有值 |
resize(ArrayIndex size) |
调整值的大小 |
append(Json::Value val) |
将值追加到值末尾 |
get(ArrayIndex index, Json::Value default) |
返回给定索引处的值或默认值 |
isMember(std::string &key) |
标识值是否包含具有给定键的值 |
removeMember(std::string &key) |
删除具有给定键的值 |
toStyledString() |
返回包含值的值的字符串 |
只要将 Json::Value
视为一个包含命名 Json::Value
的映射,这些函数就易于使用和理解。例如,假设一个名为 root
的 Json::Value
是由以下 JSON 对象创建的:{ "num": 1, "obj": { "str": "Hi" }}
。
root.size()
返回2
,因为对象包含两个值。root["num"].type()
返回1
,这对应于Json::uintValue
。root["obj"].toStyledString()
返回{"str":"Hi"}
。root["obj"]["str"]
返回Hi!
,因为root["obj"]
是一个对象,其str
属性的值为Hi!
。
最后一个示例演示了如何访问 Json::Value
中的嵌套对象。此外,可以通过与向 C++ 映射添加键/值对相同的方式将值添加到 Json::Value
。例如,可以使用 root["five"] = 5
将 `"five"`/`5` 对添加到 root
。
5. 写入 JSON 数据
Json::Writer
类只有一个公共函数 write
,它接受一个 Json::Value
并返回一个 std::string
。此函数是虚拟的,因此如果您想调用 write
,则需要使用 Json::Writer
的两个子类之一:
Json::FastWriter
- 以单行压缩文本输出 JSON。Json::StyledWriter
- 按需输出多行、易于阅读的 JSON。
为了了解如何使用它们,假设 root
是一个 Json::Value
,其数据包含 { "num": 1, "obj": { "str": "Hi" }}
。如果 writer
是一个 Json::FastWriter
,则 writer.write(root)
函数将返回字符串 {"num":1,"obj":{"str":"Hi"}}
。Json::FastWriter
的唯一另一个函数是 enableYAMLCompatibility()
,它确保每个冒号后面都跟着一个空格。
如果 writer
是一个 Json::StyledWriter
,则 writer.write(root)
函数将返回以下字符串:
{ "num" : 1, "obj" : { "str" : "Hi" } }
如所示,Json::StyledWriter
在新行上打印每个键值对。此外,每个嵌套值都已缩进。如果值为一个空对象,则编写器将打印 {}
,而不进行缩进或换行。
除了 Json::FastWriter
和 Json::StyledWriter
之外,JsonCpp 还提供了一个名为 Json::StyledStreamWriter
的类。它不是 Json::Writer
的子类,但它确实有一个 write
函数:
write(std::basic_ostream<char, std::char_traits<char>>& out, const Json::Value& root);
这会将 Json::Value
中的数据写入给定的输出流。这在您想将数据写入文件时特别有用,下一节将演示如何执行此操作。
6. 示例应用程序
本文提供了一个示例项目,演示了如何使用 JsonCpp 工具集读取、处理和写入 JSON 数据。jsoncpp_demo.zip 存档包含一个名为 jsoncpp_demo.cpp 的文件。其源代码如下:
#include <cstdlib> #include <fstream> #include <iostream> #include "json/json.h" int main(void) { Json::Reader reader; Json::Value root; Json::StyledStreamWriter writer; std::string text = "{ \"first\": \"James\", \"last\": \"Bond\", \"nums\": [0, 0, 7] }"; std::ofstream outFile; // Parse JSON and print errors if needed if(!reader.parse(text, root)) { std::cout << reader.getFormattedErrorMessages(); exit(1); } else { // Read and modify the json data std::cout << "Size: " << root.size() << std::endl; std::cout << "Contains nums? " << root.isMember("nums") << std::endl; root["first"] = "Jimmy"; root["middle"] = "Danger"; // Write the output to a file outFile.open("output.json"); writer.write(outFile, root); outFile.close(); } return 0; }
应用程序首先解析字符串中的数据。如果遇到任何错误,getFormattedErrorMessages
会将错误打印到标准输出,然后应用程序退出。
如果 parse
成功完成,应用程序将打印 Json::Value
的大小,并检查它是否包含名为 nums
的成员。然后,它将更改与 first
键对应的值,并添加一个对应于 middle
键的值。
处理完 JSON 数据后,应用程序将为名为 output.json 的文件打开一个输出流。然后,它调用 Json::StyledStreamWriter
的 write
函数将 JSON 数据打印到文件中。
该项目包含一个用于构建 jsoncpp_demo 的 Makefile。它假定 JsonCpp 库 libjsoncpp.a 位于 /home/Matt/jsoncpp/lib 目录中。它还假定所需的头文件位于 /home/Matt/jsoncpp/include 目录中。要在您的开发系统上构建演示,必须更改这些位置。
历史
2016/5/27 - 首次提交文章,在文章中添加了示例代码,修复了副标题