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

使用 C++ 访问 JSON 数据

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.71/5 (9投票s)

2016年5月27日

CPOL

8分钟阅读

viewsIcon

169564

downloadIcon

2859

本文档介绍了如何使用 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 构建系统。然后,您可以按照四个步骤构建库:

  1.  切换到包含 JsonCpp 源代码的目录,并创建一个目录来存放构建文件:mkdir -p build/debug
  2.  切换到新目录:cd build/debug
  3.  运行 CMake:cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON \
    -DBUILD_SHARED_LIBS=OFF -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
  4.  构建工具集:make

如果此过程成功完成,build/debug 文件夹将在其 src/lib_json 文件夹中包含一个静态库 libjsoncpp.a。此外,顶层 JsonCpp 文件夹包含一个名为 include 的目录,其中包含 json/json.h 和 json/writer.h 等头文件。这些是编译访问 JsonCpp 例程的 C++ 应用程序所必需的。

可以通过设置 cmake 命令的参数来配置构建。例如,CMAKE_BUILD_TYPE 可以设置为 NoneDebugReleaseRelWithDebInfoMinSizeRelCoverage。另外,如果 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::FastWriterJson::StyledWriterJson::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 要求属性名(firstsecond)用双引号括起来。如果属性名未加引号,parse 将返回 false。

4. 处理 JSON 数据

如果 parse 成功完成,就可以通过 Json::Value 对象访问 JSON 数据。这使得可以使用 C++ 映射(map)表示法访问 JSON 属性。例如,在前述代码中,属性名为 first,其值为 1。如果 rootJson::Value 的名称,则 root["first"] 将返回 1。同样,root["second"] 将返回 2。请注意,通过映射返回的值类型为 Json::Value。换句话说,Json::Value 可以看作是其他 Json::Value 的映射。

可以通过调用 type() 来获取值的类型。它返回 ValueType 枚举类型的值,该值可以是 nullValueintValueuintValuerealValuestringValuebooleanValuearrayValueobjectValue。例如,root["second"].type() 返回 Json::uintValue

表 2 列出了 typeJson::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 的映射,这些函数就易于使用和理解。例如,假设一个名为 rootJson::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 的两个子类之一:

  1. Json::FastWriter - 以单行压缩文本输出 JSON。
  2. 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::FastWriterJson::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::StyledStreamWriterwrite 函数将 JSON 数据打印到文件中。

该项目包含一个用于构建 jsoncpp_demo 的 Makefile。它假定 JsonCpp 库 libjsoncpp.a 位于 /home/Matt/jsoncpp/lib 目录中。它还假定所需的头文件位于 /home/Matt/jsoncpp/include 目录中。要在您的开发系统上构建演示,必须更改这些位置。

历史

2016/5/27 - 首次提交文章,在文章中添加了示例代码,修复了副标题

© . All rights reserved.