C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++读取JSON

C++读取JSON文件的三种方式小结(jsoncpp、nlohmann/json和RapidJSON)

作者:司徒轩宇

JSON已成为最流行的数据交换格式之一,,C++标准库并没有提供原生的JSON支持,这就需要我们借助第三方库来处理JSON数据,下面就来介绍一下读取JSON文件的三种方式,感兴趣的可以了解一下

在现代C++开发中,JSON(JavaScript Object Notation)已成为最流行的数据交换格式之一。无论是网络通信、配置文件读取还是数据持久化,JSON都扮演着重要角色。然而,C++标准库并没有提供原生的JSON支持,这就需要我们借助第三方库来处理JSON数据。

本文将详细介绍三种主流的C++ JSON库:jsoncppnlohmann/jsonRapidJSON,通过实际代码示例展示如何使用它们读取和解析JSON文件。

为什么需要JSON库?

JSON以其轻量级、易读易写的特性,在Web API、配置文件、数据存储等场景中广泛应用。在C++中处理JSON数据时,我们需要解决以下问题:

下面让我们深入了解这三种库的具体用法。

1. jsoncpp - 经典稳定的选择

jsoncpp是一个历史悠久、稳定可靠的C++ JSON库,被许多大型项目使用。

安装与配置

Ubuntu/Debian:

sudo apt-get install libjsoncpp-dev

源码编译:

git clone https://github.com/open-source-parsers/jsoncpp.git
cd jsoncpp
mkdir build && cd build
cmake .. && make && sudo make install

基本使用方法

#include <json/json.h>
#include <fstream>
#include <iostream>
#include <vector>

class JsonCppExample {
public:
    void readJsonFile(const std::string& filename) {
        // 打开文件
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "错误:无法打开文件 " << filename << std::endl;
            return;
        }

        // 创建JSON读取器
        Json::CharReaderBuilder readerBuilder;
        Json::Value root;  // 存储解析后的 JSON 数据
        std::string errs;  // 存储错误信息

        // 解析JSON
        bool success = Json::parseFromStream(readerBuilder, file, &root, &errs);
        if (!success) {
            std::cerr << "JSON解析错误: " << errs << std::endl;
            return;
        }

        // 访问基本数据类型
        if (root.isMember("name")) {
            std::string name = root["name"].asString(); //访问字符串类型
            std::cout << "姓名: " << name << std::endl;
        }

        if (root.isMember("age")) {
            int age = root["age"].asInt();  //访问整数类型
            std::cout << "年龄: " << age << std::endl;
        }

        if (root.isMember("is_student")) {
            bool isStudent = root["is_student"].asBool(); //访问布尔类型
            std::cout << "是否学生: " << (isStudent ? "是" : "否") << std::endl;
        }

        // 访问数组
        if (root.isMember("hobbies") && root["hobbies"].isArray()) {
            std::cout << "爱好: ";
            //访问数组类型
            for (const auto& hobby : root["hobbies"]) {
                std::cout << hobby.asString() << " ";  
            }
            std::cout << std::endl;
        }

        // 访问嵌套对象
        if (root.isMember("address")) {
            Json::Value address = root["address"];
            if (address.isMember("street")) {
                std::cout << "街道: " << address["street"].asString() << std::endl;
            }
            if (address.isMember("city")) {
                std::cout << "城市: " << address["city"].asString() << std::endl;
            }
        }

        // 安全访问,提供默认值
        double height = root.get("height", 170.0).asDouble();
        std::cout << "身高: " << height << "cm" << std::endl;
    }

    void writeJsonFile(const std::string& filename) {
        Json::Value root;
        
        // 设置基本值
        root["name"] = "李四";
        root["age"] = 30;
        root["is_student"] = false;
        
        // 设置数组
        Json::Value hobbies(Json::arrayValue);
        hobbies.append("旅游");
        hobbies.append("摄影");
        hobbies.append("烹饪");
        root["hobbies"] = hobbies;
        
        // 设置嵌套对象
        Json::Value address;
        address["street"] = "中山路456号";
        address["city"] = "上海";
        root["address"] = address;
        
        // 写入文件
        std::ofstream file(filename);
        if (file.is_open()) {
            Json::StreamWriterBuilder writerBuilder;
            std::unique_ptr<Json::StreamWriter> writer(writerBuilder.newStreamWriter());
            writer->write(root, &file);
            file.close();
            std::cout << "JSON文件已写入: " << filename << std::endl;
        }
    }
};

// 使用示例
int main() {
    JsonCppExample example;
    example.writeJsonFile("output.json");
    example.readJsonFile("data.json");
    return 0;
}

2. nlohmann/json - 现代易用的选择

nlohmann/json是一个单头文件的现代C++ JSON库,以其简洁的API和强大的功能而闻名。

安装与配置

单头文件方式(推荐):

// 只需下载一个头文件
#include "nlohmann/json.hpp"

包管理器安装:

# vcpkg
vcpkg install nlohmann-json

# Conan
conan install nlohmann_json/3.11.2

基本使用方法

#include <nlohmann/json.hpp>
#include <fstream>
#include <iostream>
#include <vector>

using json = nlohmann::json;

class NlohmannJsonExample {
public:
    void readJsonFile(const std::string& filename) {
        try {
            // 直接从文件读取JSON
            std::ifstream file(filename);
            if (!file.is_open()) {
                throw std::runtime_error("无法打开文件: " + filename);
            }

            json data = json::parse(file);

            // 访问基本数据类型 - 方式1:直接访问
            std::string name = data["name"];
            int age = data["age"];
            bool isStudent = data["is_student"];
            
            std::cout << "姓名: " << name << std::endl;
            std::cout << "年龄: " << age << std::endl;
            std::cout << "是否学生: " << (isStudent ? "是" : "否") << std::endl;

            // 访问基本数据类型 - 方式2:带默认值的安全访问
            double height = data.value("height", 175.5);
            std::cout << "身高: " << height << "cm" << std::endl;

            // 访问数组
            std::cout << "爱好: ";
            for (const auto& hobby : data["hobbies"]) {
                std::cout << hobby.get<std::string>() << " ";
            }
            std::cout << std::endl;

            // 访问嵌套对象
            if (data.contains("address")) {
                auto& address = data["address"];
                std::cout << "地址: " << address["city"] << ", " << address["street"] << std::endl;
            }

            // 类型检查和异常安全
            if (data["hobbies"].is_array()) {
                std::cout << "hobbies 是数组类型" << std::endl;
            }

            // 更复杂的遍历
            std::cout << "\n所有数据:" << std::endl;
            for (auto& [key, value] : data.items()) {
                std::cout << key << ": " << value << std::endl;
            }

        } catch (const json::parse_error& e) {
            std::cerr << "JSON解析错误: " << e.what() << std::endl;
        } catch (const json::type_error& e) {
            std::cerr << "类型错误: " << e.what() << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "错误: " << e.what() << std::endl;
        }
    }

    void writeJsonFile(const std::string& filename) {
        // 创建JSON对象 - 方式1:类似字典操作
        json data;
        data["name"] = "王五";
        data["age"] = 28;
        data["is_student"] = true;
        data["height"] = 180.2;

        // 创建数组
        data["hobbies"] = {"音乐", "电影", "运动"};

        // 创建嵌套对象
        data["address"] = {
            {"street", "解放路789号"},
            {"city", "广州"},
            {"postcode", "510000"}
        };

        // 添加复杂结构
        data["scores"] = {
            {"数学", 95},
            {"英语", 88},
            {"物理", 92}
        };

        // 写入文件,美化输出
        std::ofstream file(filename);
        if (file.is_open()) {
            file << data.dump(4); // 缩进4个空格
            std::cout << "JSON文件已写入: " << filename << std::endl;
        }
    }

    // 高级功能:C++类型与JSON自动转换
    struct Person {
        std::string name;
        int age;
        bool is_student;
        std::vector<std::string> hobbies;
        
        // 必须提供to_json和from_json函数
        NLOHMANN_DEFINE_TYPE_INTRUSIVE(Person, name, age, is_student, hobbies)
    };

    void useWithCustomType() {
        // 从JSON自动转换到C++结构体
        json j = R"({
            "name": "赵六",
            "age": 35,
            "is_student": false,
            "hobbies": ["读书", "写作"]
        })"_json;

        Person person = j.get<Person>();
        std::cout << "自定义类型姓名: " << person.name << std::endl;

        // 从C++结构体自动转换到JSON
        Person newPerson{"钱七", 40, true, {"编程", "数学"}};
        json newJson = newPerson;
        std::cout << "生成的JSON: " << newJson.dump(2) << std::endl;
    }
};

// 使用示例
int main() {
    NlohmannJsonExample example;
    example.writeJsonFile("nlohmann_output.json");
    example.readJsonFile("data.json");
    example.useWithCustomType();
    return 0;
}

编译命令

# 单头文件方式,只需包含路径
g++ -o nlohmann_example nlohmann_example.cpp -std=c++17

# 如果有安装到系统目录,则不需要特殊参数
g++ -o nlohmann_example nlohmann_example.cpp

3. RapidJSON - 高性能的选择

RapidJSON专注于高性能和低内存占用,特别适合对性能要求严格的场景。

安装与配置

源码集成:

git clone https://github.com/Tencent/rapidjson.git

包管理器:

# vcpkg
vcpkg install rapidjson

基本使用方法

#include "rapidjson/document.h"
#include "rapidjson/istreamwrapper.h"
#include "rapidjson/ostreamwrapper.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <fstream>
#include <iostream>
#include <vector>

class RapidJsonExample {
public:
    void readJsonFile(const std::string& filename) {
        // 打开文件
        std::ifstream ifs(filename);
        if (!ifs.is_open()) {
            std::cerr << "错误:无法打开文件 " << filename << std::endl;
            return;
        }

        // 使用流包装器读取JSON
        rapidjson::IStreamWrapper isw(ifs);
        rapidjson::Document document;
        
        // 解析JSON
        document.ParseStream(isw);
        
        // 检查解析错误
        if (document.HasParseError()) {
            std::cerr << "解析错误 (offset " << document.GetErrorOffset() 
                      << "): " << rapidjson::GetParseError_En(document.GetParseError()) 
                      << std::endl;
            return;
        }

        // 检查根对象类型
        if (!document.IsObject()) {
            std::cerr << "错误:根元素不是对象" << std::endl;
            return;
        }

        // 安全访问字符串成员
        if (document.HasMember("name") && document["name"].IsString()) {
            std::string name = document["name"].GetString();
            std::cout << "姓名: " << name << std::endl;
        }

        // 安全访问整数成员
        if (document.HasMember("age") && document["age"].IsInt()) {
            int age = document["age"].GetInt();
            std::cout << "年龄: " << age << std::endl;
        }

        // 安全访问布尔成员
        if (document.HasMember("is_student") && document["is_student"].IsBool()) {
            bool isStudent = document["is_student"].GetBool();
            std::cout << "是否学生: " << (isStudent ? "是" : "否") << std::endl;
        }

        // 访问数组
        if (document.HasMember("hobbies") && document["hobbies"].IsArray()) {
            const rapidjson::Value& hobbies = document["hobbies"];
            std::cout << "爱好: ";
            for (rapidjson::SizeType i = 0; i < hobbies.Size(); i++) {
                if (hobbies[i].IsString()) {
                    std::cout << hobbies[i].GetString() << " ";
                }
            }
            std::cout << std::endl;
        }

        // 访问嵌套对象
        if (document.HasMember("address") && document["address"].IsObject()) {
            const rapidjson::Value& address = document["address"];
            if (address.HasMember("street") && address["street"].IsString()) {
                std::cout << "街道: " << address["street"].GetString() << std::endl;
            }
            if (address.HasMember("city") && address["city"].IsString()) {
                std::cout << "城市: " << address["city"].GetString() << std::endl;
            }
        }

        // 处理可能不存在的字段(带默认值)
        double height = 170.0; // 默认值
        if (document.HasMember("height") && document["height"].IsDouble()) {
            height = document["height"].GetDouble();
        }
        std::cout << "身高: " << height << "cm" << std::endl;
    }

    void writeJsonFile(const std::string& filename) {
        rapidjson::Document document;
        document.SetObject();
        rapidjson::Document::AllocatorType& allocator = document.GetAllocator();

        // 添加字符串成员
        rapidjson::Value name;
        name.SetString("孙八", allocator);
        document.AddMember("name", name, allocator);

        // 添加数字成员
        document.AddMember("age", 22, allocator);
        document.AddMember("height", 175.5, allocator);

        // 添加布尔成员
        document.AddMember("is_student", true, allocator);

        // 添加数组
        rapidjson::Value hobbies(rapidjson::kArrayType);
        rapidjson::Value hobby1, hobby2, hobby3;
        hobby1.SetString("游戏", allocator);
        hobby2.SetString("编程", allocator);
        hobby3.SetString("音乐", allocator);
        hobbies.PushBack(hobby1, allocator);
        hobbies.PushBack(hobby2, allocator);
        hobbies.PushBack(hobby3, allocator);
        document.AddMember("hobbies", hobbies, allocator);

        // 添加嵌套对象
        rapidjson::Value address(rapidjson::kObjectType);
        rapidjson::Value street, city;
        street.SetString("和平路101号", allocator);
        city.SetString("深圳", allocator);
        address.AddMember("street", street, allocator);
        address.AddMember("city", city, allocator);
        document.AddMember("address", address, allocator);

        // 写入文件
        std::ofstream ofs(filename);
        if (ofs.is_open()) {
            rapidjson::OStreamWrapper osw(ofs);
            rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
            document.Accept(writer);
            std::cout << "JSON文件已写入: " << filename << std::endl;
        }
    }

    // 高级用法:使用StringBuffer
    void useStringBuffer() {
        rapidjson::Document document;
        document.SetObject();
        rapidjson::Document::AllocatorType& allocator = document.GetAllocator();

        document.AddMember("message", "Hello, RapidJSON!", allocator);
        document.AddMember("value", 42, allocator);

        // 使用StringBuffer生成JSON字符串
        rapidjson::StringBuffer buffer;
        rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
        document.Accept(writer);

        std::cout << "生成的JSON字符串: " << buffer.GetString() << std::endl;
    }
};

// 使用示例
int main() {
    RapidJsonExample example;
    example.writeJsonFile("rapidjson_output.json");
    example.readJsonFile("data.json");
    example.useStringBuffer();
    return 0;
}

编译命令

g++ -o rapidjson_example rapidjson_example.cpp -I/path/to/rapidjson/include

三种库的详细对比

为了帮助您选择合适的JSON库,下面从多个维度进行详细对比:

性能对比

测试场景jsoncppnlohmann/jsonRapidJSON
解析速度中等较慢最快
内存占用中等较高最低
序列化速度中等较慢最快

API易用性对比

特性jsoncppnlohmann/jsonRapidJSON
学习曲线平缓非常平缓陡峭
代码简洁性中等最优繁琐
类型安全中等优秀需要手动检查
错误处理返回bool异常机制返回错误码

到此这篇关于C++读取JSON文件的三种方式小结(jsoncpp、nlohmann/json和RapidJSON)的文章就介绍到这了,更多相关C++读取JSON内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文