使用 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 - 首次提交文章,在文章中添加了示例代码,修复了副标题


