JSON 学习笔记

QJson (Qt) vs. nlohmann/json (现代 C++)

在 C++ 开发中,JSON 的处理方式主要分为两类:基于 DOM 树的操作(如 Qt 的 QJson)和 基于对象映射的操作(如 nlohmann/json)。

核心概念对比

特性 QJson (Qt 框架) nlohmann/json (现代 C++)
库类型 Qt 核心模块的一部分,强依赖 Qt 完整开发环境 Header-only(单头文件),无任何外部依赖,开箱即用
操作风格 显式容器转换 (Object -> Map) 类原生对象操作
(像操作 C++ 原生结构体/数组一样操作 JSON 数据)
序列化/反序列化 手动逐个键值对调用接口提取/插入数据 自动序列化 (通过宏映射结构体)
语法糖 & 语法简洁度 语法糖较重,使用时需要频繁调用类型转换方法
.toObject().toArray().toString().toInt()
语法糖极度轻量优雅,原生支持 C++ 初始化列表

使用示例

1. QJson 构建JSON对象(手动+频繁类型转换)

QJsonObject jsonObj;
jsonObj.insert("name", QJsonValue(QString("QtJson")));
jsonObj.insert("version", QJsonValue(6.7));
jsonObj.insert("is_valid", QJsonValue(true));

QJsonArray arr;
arr.append(QJsonValue(1));
arr.append(QJsonValue(2));
jsonObj.insert("nums", QJsonValue(arr));

// 读取时还需要显式转换
QString name = jsonObj["name"].toString();
int num = jsonObj["nums"].toArray()[0].toInt();

2. nlohmann/json 构建JSON对象(类原生+初始化列表)

#include "nlohmann/json.hpp"
using json = nlohmann::json;

// 原生初始化列表写法,无任何转换调用,像写结构体一样
json jsonObj = {
    {"name", "nlohmann_json"},
    {"version", 3.11.2},
    {"is_valid", true},
    {"nums", {1, 2, 3}}
};

// 读取时类原生访问,无需转换
std::string name = jsonObj["name"];
int num = jsonObj["nums"][0];

3. nlohmann/json 结构体自动序列化(核心优势)

// 定义普通C++结构体
struct User {
    std::string name;
    int age;
    std::vector<int> scores;
};
// 宏一键映射,完成序列化规则绑定
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, name, age, scores)

// 自动序列化/反序列化
User u{"Tom", 20, {90, 85}};
json j = u; // 结构体 → JSON,自动完成
User u2 = j; // JSON → 结构体,自动完成

对比XML

XML必须严格遵循标签结构,且解析时通常需要处理节点、属性和文本的层级关系。

<!-- 数据表示 -->
<User id="1001">
    <Name>Alice</Name>
    <Age>25</Age>
    <Tags>
        <Item>Vision</Item>
        <Item>C++</Item>
    </Tags>
</User>
// C++ 解析 XML 的伪代码 
tinyxml2::XMLDocument doc;
doc.LoadFile("user.xml");
auto* user = doc.FirstChildElement("User");
const char* name = user->FirstChildElement("Name")->GetText();
int age = user->FirstChildElement("Age")->IntText();
// 获取列表需要循环遍历节点
std::vector<std::string> tags;
for (auto* item = user->FirstChildElement("Tags")->FirstChildElement("Item"); 
     item != nullptr; item = item->NextSiblingElement("Item")) {
    tags.push_back(item->GetText());
}

XML 的痛点:结构冗余(每个数据都要写开头和结尾标签);解析复杂(通常需要逐级遍历节点,或者使用复杂的 XPath);类型缺失(所有内容本质都是字符串,需手动转换)。

nlohmann/json 深度解析

nlohmann/json 被誉为 "JSON for Modern C++",其核心优势在于极其直观的语法,几乎让你感觉在写 Python。

1.基础用法与 CRUD

该库重载了 [] 和 << 等操作符,使得操作 JSON 像操作 std::map 一样简单。

#include <nlohmann/json.hpp>
using json = nlohmann::json;

// 1. 创建 JSON 对象 (使用初始化列表)
json j = {
  {"pi", 3.141},
  {"name", "Niels"},
  {"answer", {{"everything", 42}}},
  {"list", {1, 0, 2}}
};

// 2. 增加/修改
j["new_key"] = "new_value";

// 3. 查询
if (j.contains("name")) {
    std::string name = j["name"];
}

// 4. 序列化
std::string s = j.dump(4);

2.进阶:非STL容器/第三方库支持 (adl_serializer)

当你使用第三方库(如 OpenCV、Qt)时,由于无法修改它们的源码来添加宏,需要通过特化adl_serializer来告诉 json 库如何处理这些类型。

A.处理 OpenCV 类型 (cv::Point3f)

namespace nlohmann {
    template <>
    struct adl_serializer<cv::Point3f> {
        static void to_json(json& j, const cv::Point3f& pt) {
            j = json::array({pt.x, pt.y, pt.z});
        }
        static void from_json(const json& j, cv::Point3f& pt) {
            pt.x = j[0].get<float>();
            pt.y = j[1].get<float>();
            pt.z = j[2].get<float>();
        }
    };
}

B. 处理 Qt 常用类型 (QString, QDateTime, QVariant)

  • QString: 自动转换 Qt 的 UTF-16 字符串与标准 std::string。

  • QDateTime: 将日期时间自动序列化为符合 ISO 8601 标准的字符串(如 2023-10-27T10:00:00.000)。

  • QVariant: 能够根据存储的实际类型(int, bool, double, Map 等)动态选择 JSON 类型。

namespace nlohmann {
    // QString 序列化支持
    template <>
    struct adl_serializer<QString> {
        static void to_json(json& j, const QString& str) {
            j = str.toStdString();
        }
        static void from_json(const json& j, QString& str) {
            str = QString::fromStdString(j.get<std::string>());
        }
    };

    // QDateTime 序列化支持
    template <>
    struct adl_serializer<QDateTime> {
        static void to_json(json& j, const QDateTime& dt) {
            j = dt.toString(Qt::ISODateWithMs).toStdString();
        }
        static void from_json(const json& j, QDateTime& dt) {
            dt = QDateTime::fromString(QString::fromStdString(j.get<std::string>()), Qt::ISODateWithMs);
        }
    };
}

3.核心功能:自定义结构体映射 (宏)

  1. 侵入式定义 (在结构体内): 适合可以修改结构体源码的情况。使用 NLOHMANN_DEFINE_TYPE_INTRUSIVE 宏直接在类定义中注册成员。
struct Person {
    QString name; // 配合 3.2 中的 adl_serializer 即可直接使用 QString
    int age;
    NLOHMANN_DEFINE_TYPE_INTRUSIVE(Person, name, age)
};
  1. 非侵入式定义 (在结构体外)
    适合无法修改源码的第三方结构体或库。使用 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE 宏在类定义外部进行注册。
// 假设这是一个外部库定义的结构体,无法修改其 header 文件
struct ExternalUser {
    std::string id;
    double score;
};

// 在全局命名空间或对应的命名空间内进行注册
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ExternalUser, id, score)
posted @ 2026-01-19 20:46  一楼二栋  阅读(0)  评论(0)    收藏  举报