C++中JSON序列化和反序列化的实现

1. 引言

序列化 (Serialization)是指将程序中的内存对象(如结构体、类实例、列表等)转换成一种可以存储或传输的格式(通常是字节流或文本)的过程。常见的序列化格式包括:

  • JSON(文本,人类可读)
  • XML
  • Protocol Buffers(二进制,高效)
  • MessagePack
  • YAML
  • 甚至自定义的二进制格式

反序列化 (Deserialization)是序列化的逆过程:将序列化后的数据 (如 JSON 字符串)重新转换回程序中的内存对象

2. 原因

那么,为什么需要序列化和反序列化呢?笔者的体会是不序列化/反序列化也行,只不过在进行相应的读取和写入操作的时候会非常繁琐。比如一个配置文件更新:

假设我们有一个博客系统的配置,包含站点标题、作者名、是否启用评论、默认封面地址等多个字段。如果不使用序列化机制,每次读取配置时,就得手动打开 JSON(或 YAML、INI)文件,逐行解析,判断每个字段是否存在、类型是否正确,再一一赋值给程序中的变量;而当用户修改了某个设置、需要保存回文件时,又得手动拼接字符串、处理引号转义、数组格式、缩进对齐......稍有不慎,就会生成格式错误的文件,导致程序下次启动失败。

更麻烦的是,一旦配置结构发生变化------比如新增一个 theme 字段,或者把 enableComments 拆成 enablePostComments 和 enablePageComments------我们就不得不同时修改读取逻辑、写入逻辑、默认值初始化、错误处理等多处代码,极易遗漏或出错。

而有了序列化和反序列化,这一切就变得简洁而可靠:定义好结构体(或类),注册对应的转换函数,一行代码就能从 JSON 构造出完整的配置对象,另一行代码就能把修改后的对象写回文件。字段增减只需调整结构体,读写逻辑几乎无需改动,既减少了重复劳动,也大大提升了健壮性和可维护性。

因此,序列化/反序列化并非"必须",但它是对抗复杂性、提升开发效率和系统可靠性的重要工具。

3. 实现

3.1 示例

这里就举例实现一下 C++ 中 JSON 序列化和反序列化。如果要反序列化成 C++ 可以使用的内存对象,最好的数据容器就是 struct 。当然 class 也可以,不过我们都知道 struct 和 class 的区别:struct 的成员一般默认是公有的,而 class 的成员一般默认是私有的。因此,struct 适合以数据成员为主的,比较简单的对象;class 适合以方法成员为主的,比较复杂的对象。

笔者使用 nlohmann/json 中序列化/反序列化一个表达博客的数据对象:

BlogMeta.h

cpp 复制代码
#include <nlohmann/json.hpp>
#include <string>
#include <vector>

#include "PostStats.h"

namespace DataTransfer {

/// @brief 一篇博客的元数据
struct BlogMeta {
  std::string id;
  std::string title;
  std::string summary;
  std::string createdTime;
  std::string updatedTime;  
  std::string coverAddress;
  int64_t isDraft;
  std::vector<std::string> tagNames;
  std::vector<std::string> categoryNames;
  PostStats postStats;

  // 提供序列化接口
  friend void to_json(nlohmann::json& j, const BlogMeta& s);

  // 提供反序列化接口
  friend void from_json(const nlohmann::json& j, BlogMeta& s);
};

BlogMeta.cpp

cpp 复制代码
#include "BlogMeta.h"

#include "Util/GetTime.h"

namespace DataTransfer {

// 提供序列化接口
void to_json(nlohmann::json& j, const BlogMeta& s) {
  j = nlohmann::json{{"id", s.id},
                     {"title", s.title},
                     {"summary", s.summary},
                     {"createdTime", s.createdTime},
                     {"updatedTime", s.updatedTime},
                     {"coverAddress", s.coverAddress},
                     {"isDraft", s.isDraft},
                     {"tagNames", s.tagNames},
                     {"categoryNames", s.categoryNames},
                     {"postStats", s.postStats}};
}

// 提供反序列化接口
void from_json(const nlohmann::json& j, BlogMeta& s) {
  s.id = j.at("id").get<std::string>();
  s.title = j.at("title").get<std::string>();
  s.summary = j.at("summary").get<std::string>();
  s.createdTime = j.at("createdTime").get<std::string>();
  s.updatedTime =
      j.value("updatedTime", Util::GetCurrentTime());  //提供默认值,增加稳定性
  s.coverAddress = j.at("coverAddress").get<std::string>();
  s.isDraft = j.at("isDraft").get<int64_t>();
  s.tagNames = j.at("tagNames").get<std::vector<std::string>>();
  s.categoryNames = j.at("categoryNames").get<std::vector<std::string>>();
  s.postStats = j.at("postStats").get<PostStats>();
}

}  // namespace DataTransfer

其中 to_json / from_json 就是对应的 序列化 / 反序列化接口。只需要填充这一对接口,就可以通过赋值运算符=实现自动序列化和反序列化:

cpp 复制代码
#include <iostream>
#include <nlohmann/json.hpp>
#include "BlogMeta.h"  // 包含你的 BlogMeta 定义

int main() {
    using namespace DataTransfer;
    using json = nlohmann::json;

    // ----------------------------
    // 1. 创建一个 BlogMeta 对象(内存中的数据)
    // ----------------------------
    BlogMeta blog;
    blog.id = "blog-001";
    blog.title = "深入理解 C++ 序列化";
    blog.summary = "本文介绍如何使用 nlohmann/json 进行安全高效的序列化。";
    blog.createdTime = "2025-12-22T10:00:00Z";
    blog.updatedTime = "2025-12-22T17:00:00Z";  // 可省略,反序列化时会用默认值
    blog.coverAddress = "/images/cpp_serialization.jpg";
    blog.isDraft = 0;
    blog.tagNames = {"C++", "JSON", "Serialization"};
    blog.categoryNames = {"Programming", "Tutorial"};
    blog.postStats = {1200, 85, 23};

    // ----------------------------
    // 2. 序列化:BlogMeta → JSON
    // ----------------------------
    json j = blog;  // 自动调用 to_json
    std::cout << "【序列化结果】\n" << j.dump(2) << "\n\n";

    // ----------------------------
    // 3. 反序列化:JSON → BlogMeta
    // ----------------------------
    // 模拟从文件或网络接收到的 JSON(故意省略 updatedTime)
    std::string jsonStr = R"({
        "id": "blog-002",
        "title": "反序列化的健壮性测试",
        "summary": "测试缺失 updatedTime 字段时的行为",
        "createdTime": "2025-12-20T08:30:00Z",
        "coverAddress": "/images/robust.png",
        "isDraft": 1,
        "tagNames": ["C++", "Robustness"],
        "categoryNames": ["Engineering"],
        "postStats": {
            "viewCount": 450,
            "likeCount": 30,
            "commentCount": 5
        }
    })";
 
    BlogMeta restoredBlog = json::parse(jsonStr);  // 自动调用 from_json
  
    // ----------------------------
    // 4. 验证反序列化结果
    // ----------------------------
    std::cout << "【反序列化结果】\n";
    std::cout << "ID: " << restoredBlog.id << "\n";
    std::cout << "Title: " << restoredBlog.title << "\n";
    std::cout << "UpdatedTime (应为当前默认值): " << restoredBlog.updatedTime << "\n";
    std::cout << "Is Draft: " << restoredBlog.isDraft << "\n";
    std::cout << "Tags: ";
    for (const auto& tag : restoredBlog.tagNames) {
        std::cout << tag << " ";
    }
    std::cout << "\n";

    return 0;
}

3.2 能力

序列化 / 反序列化的能力取决于所使用的库实现的程度。例如这个例子中,std 容器 std::vector<std::string> 也可以直接使用赋值运算符=来序列化/反序列化吗?确实是可以的,nlohmann/json 支持所有基础类型以及绝大多数 std 容器类型的直接 序列化/ 反序列化。不过默认情况下,C++ 数据类型与JSON的对应关系是:std::string 对应字符串, int64_t, uint64_t 或 double 对应数字, std::map 对应对象, std::vector 对应数组,且 bool 对应布尔值。

另外,nlohmann/json 还支持嵌套序列化。比如这里的 PostStats 其实也是个自定义的 struct ,也实现了 to_json / from_json 接口,因此可以直接通过赋值运算符=相互转换。

PostStats.h:

cpp 复制代码
#pragma once

#include <nlohmann/json.hpp>

namespace DataTransfer {

/// @brief 一篇博客的统计信息
struct PostStats {
  int64_t viewCount = 0;     // 阅读量
  int64_t likeCount = 0;     // 点赞量
  int64_t commentCount = 0;  // 评论量

  // 提供序列化接口
  friend void to_json(nlohmann::json& j, const PostStats& s);

  // 提供反序列化接口
  friend void from_json(const nlohmann::json& j, PostStats& s);
};

}  // namespace DataTransfer

PostStats.cpp:

cpp 复制代码
#include "PostStats.h"

namespace DataTransfer {

// 提供序列化接口
void to_json(nlohmann::json& j, const PostStats& s) {
  j = nlohmann::json{{"viewCount", s.viewCount},
                     {"likeCount", s.likeCount},
                     {"commentCount", s.commentCount}};
}

// 提供反序列化接口
void from_json(const nlohmann::json& j, PostStats& s) {
  s.viewCount = j.at("viewCount").get<int64_t>();
  s.likeCount = j.at("likeCount").get<int64_t>();
  s.commentCount = j.at("commentCount").get<int64_t>();
}

}  // namespace DataTransfer

3.3 兼容

如果 JSON 配置文件进行了升级,比如增加了一个新的配置字段,那么新的程序读取旧的配置文件就有可能出错,所以在反序列化的时候最好使用 value() 提供默认值:

cpp 复制代码
void from_json(const nlohmann::json& j, BlogMeta& s) {
  s.id = j.value("id", std::string{});
  s.title = j.value("title", std::string{});
  s.summary = j.value("summary", std::string{});
  s.createdTime = j.value("createdTime", std::string{});
  s.updatedTime = j.value("updatedTime", std::string{}); // 若缺失则为空字符串
  s.coverAddress = j.value("coverAddress", std::string{});
  s.isDraft = j.value("isDraft", int64_t{0});
  s.tagNames = j.value("tagNames", std::vector<std::string>{});
  s.categoryNames = j.value("categoryNames", std::vector<std::string>{});

  // 对于嵌套结构如 PostStats,如果它也支持 from_json,可这样处理:
  if (j.contains("postStats") && j["postStats"].is_object()) {
    s.postStats = j["postStats"].get<PostStats>();
  } else {
    s.postStats = PostStats{}; // 默认构造
  }
}

另外,如果业务逻辑允许,能分清楚哪些是必须字段,哪些是可选字段,那么可以使用std::optional:

cpp 复制代码
/// @brief 一篇博客的元数据(健壮版,支持可选字段)
struct BlogMeta {
    std::string id;                          // 必填
    std::string title;                       // 必填
    std::optional<std::string> summary;
    std::string createdTime;                 // 通常必填,也可改为 optional
    std::optional<std::string> updatedTime;  
    std::optional<std::string> coverAddress;
    std::optional<int64_t> isDraft;          // 可选,默认 false 或 0
    std::optional<std::vector<std::string>> tagNames;
    std::optional<std::vector<std::string>> categoryNames;
    std::optional<PostStats> postStats;

    // 序列化/反序列化友元声明
    friend void to_json(nlohmann::json& j, const BlogMeta& s);
    friend void from_json(const nlohmann::json& j, BlogMeta& s);
};

void to_json(nlohmann::json& j, const BlogMeta& s) {
    j = nlohmann::json{
        {"id", s.id},
        {"title", s.title},
        {"summary", s.summary},
        {"createdTime", s.createdTime},
        {"updatedTime", s.updatedTime},
        {"coverAddress", s.coverAddress},
        {"isDraft", s.isDraft},
        {"tagNames", s.tagNames},
        {"categoryNames", s.categoryNames},
        {"postStats", s.postStats}
    };
}

void from_json(const nlohmann::json& j, BlogMeta& s) {
    // 必填字段:使用 at(),缺失时报错
    s.id = j.at("id").get<std::string>();
    s.title = j.at("title").get<std::string>();
    s.createdTime = j.at("createdTime").get<std::string>(); // 若你也想让它可选,改用 value 或 optional

    // 可选字段:直接赋值,nlohmann 自动处理存在性
    if (j.contains("summary"))       s.summary = j["summary"].get<std::string>();
    if (j.contains("updatedTime"))   s.updatedTime = j["updatedTime"].get<std::string>();
    if (j.contains("coverAddress"))  s.coverAddress = j["coverAddress"].get<std::string>();
    if (j.contains("isDraft"))       s.isDraft = j["isDraft"].get<int64_t>();
    if (j.contains("tagNames"))      s.tagNames = j["tagNames"].get<std::vector<std::string>>();
    if (j.contains("categoryNames")) s.categoryNames = j["categoryNames"].get<std::vector<std::string>>();
    if (j.contains("postStats"))     s.postStats = j["postStats"].get<PostStats>();
}
相关推荐
挖矿大亨2 小时前
c++中值传递时是如何触发拷贝构造函数的
开发语言·c++
救救孩子把2 小时前
记录份Docker daemon.json配置-Docker镜像加速
docker·容器·json
顶点多余2 小时前
继承和多态
c++·servlet
oioihoii3 小时前
C++多线程中join与detach机制深度解析
java·jvm·c++
初圣魔门首席弟子3 小时前
智能指针使用bug
c++·算法
博大世界3 小时前
Matlab操作Json文件
json
闻缺陷则喜何志丹3 小时前
【组合数学 动态规划】P6870 [COCI2019-2020#5] Zapina|普及+
c++·数学·算法·动态规划·组合数学
CoderCodingNo3 小时前
【GESP】C++五级真题(贪心考点) luogu-B3872 [GESP202309 五级] 巧夺大奖
开发语言·c++
唐·柯里昂7983 小时前
[rk3566AI模型部署]泰山派buildroot部署yolov5 使用rknn_model_zoo
c语言·c++·笔记·yolo·rk3566·瑞芯微·泰山派