找回密码
立即注册
搜索
发回帖 发新帖

6236

积分

0

好友

784

主题
发表于 15 小时前 | 查看: 1| 回复: 0

这是反射系列的最后一篇。前面几篇依次介绍了 ^^ 与 [: :]、<meta> 查询、编译期类型合成、动态访问,以及用反射实现的通用 formatter。

前面是把 C++ 对象中的字段读出来,今天我们反过来,把 JSON 中的字段映射到 C++ 结构体。这个例子会串起成员查询、template for 和成员拼接。

假设有这样一个类型:

struct User {
    int id;
    std::string name;
};

要从 JSON 中读取它,通常要手动写出 "id" 对应 user.id、"name" 对应 user.name。以后给 User 增加字段,解析代码也得跟着改。宏或代码生成器能减少重复劳动,而 C++26 静态反射给出了另一条路:在编译期查询成员,再为每个成员生成解析代码。

本文只实现 JSON → C++ 的反序列化。JSON 文本解析交给现有 JSON 库;这里的重点是,拿到 JSON 对象后,如何把它映射到结构体。

核心思路

JSON 的键是运行时字符串,C++ 的成员则在编译期确定。我们需要把两者接起来:

  1. 在编译期取得结构体的数据成员及其名称。
  2. 为每个成员生成一段代码。
  3. 在运行时检查 JSON 是否包含同名键。
  4. 如果存在,把 JSON 中的值赋给对应成员。

这里不会在运行时通过字符串“寻找 C++ 成员”。template for 会针对每个成员分别展开,成员访问也会在编译期确定。运行时需要做的只是查找 JSON 键、读取值。

Json 接口

为了把注意力放在反射上,先假设已有这样一个 JsonObject 接口:

struct JsonObject {
    bool contains(std::string_view key) const;

    template <typename T>
    T get(std::string_view key) const;

    JsonObject get_child(std::string_view key) const;

    std::vector<JsonObject> get_array(std::string_view key) const;
};

contains 检查键是否存在,get<T> 读取基本类型,get_child 和 get_array 分别取得嵌套对象和对象数组。

基本类型

先写一个只处理 int、float、std::string 等字段的 from_json<T>。成员查询返回的是 vector;为了给 template for 使用,我们把反射信息复制进固定大小的 std::array:

#include <meta>
#include <array>
#include <cstddef>

template <typename T>
consteval auto get_member_reflections()
{
    constexpr auto ctx = std::meta::access_context::current();
    constexpr std::size_t count =
        std::meta::nonstatic_data_members_of(^^T, ctx).size();

    auto members =
        std::meta::nonstatic_data_members_of(^^T, ctx);

    std::array<std::meta::info, count> result{};

    for (std::size_t i = 0; i < count; ++i) {
        result[i] = members[i];
    }

    return result;
}

有了成员列表,反序列化函数可以这样写:

template <typename T>
T from_json(const JsonObject& json)
{
    T obj{};
    constexpr auto members = get_member_reflections<T>();

    template for (constexpr auto member : members)
    {
        constexpr auto name =
            std::meta::identifier_of(member);

        using FieldType =
            typename [: std::meta::type_of(member) :];

        if (json.contains(name)) {
            obj.[: member :] = json.get<FieldType>(name);
        }
    }

    return obj;
}

关键在执行成员访问与赋值的那一行:

obj.[: member :] = json.get<FieldType>(name);

假如本次展开对应 User::id,那么 name 是 "id",FieldType 是 int,成员访问对应 obj.id。JSON 键是否存在仍在运行时判断,但访问哪个 C++ 成员,编译器已经确定。

复杂字段

基本版本还不能直接处理嵌套结构体、std::optional 和 std::vector。我们可以保留刚才的成员遍历,在循环体内用 if constexpr 根据字段类型选择解析方式。

先准备几个类型萃取:

#include <optional>
#include <type_traits>
#include <vector>

template <typename T>
struct is_optional : std::false_type {};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};

template <typename T>
struct is_vector : std::false_type {};

template <typename T, typename Allocator>
struct is_vector<std::vector<T, Allocator>> : std::true_type {};

template <typename T>
struct inner_type;

template <typename T>
struct inner_type<std::optional<T>> {
    using type = T;
};

template <typename T, typename Allocator>
struct inner_type<std::vector<T, Allocator>> {
    using type = T;
};

对于 std::optional<int>,取出内部的 int 后即可读取 JSON 值;如果 JSON 缺少该键,就保留空值。对于 std::vector<Vector2>,则遍历 JSON 对象数组,对每个元素递归调用 from_json<Vector2>。

下面把这些分支放进同一个函数。

完整示例

这个例子用 std::map 和 std::any 模拟 JSON 对象,方便集中展示反射部分。它没有解析 JSON 字符串;实际项目需要由 JSON 库完成文本解析,并提供与这里相应的取值接口。

#include <meta>

#include <any>
#include <array>
#include <cstddef>
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

struct JsonObject {
    std::map<std::string, std::any> fields;

    bool contains(std::string_view key) const
    {
        return fields.contains(std::string(key));
    }

    template <typename T>
    T get(std::string_view key) const
    {
        return std::any_cast<T>(fields.at(std::string(key)));
    }

    JsonObject get_child(std::string_view key) const
    {
        return std::any_cast<JsonObject>(
            fields.at(std::string(key))
        );
    }

    std::vector<JsonObject> get_array(
        std::string_view key
    ) const
    {
        return std::any_cast<std::vector<JsonObject>>(
            fields.at(std::string(key))
        );
    }
};

template <typename T>
struct is_optional : std::false_type {};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};

template <typename T>
struct is_vector : std::false_type {};

template <typename T, typename Allocator>
struct is_vector<std::vector<T, Allocator>>
    : std::true_type {};

template <typename T>
struct inner_type;

template <typename T>
struct inner_type<std::optional<T>> {
    using type = T;
};

template <typename T, typename Allocator>
struct inner_type<std::vector<T, Allocator>> {
    using type = T;
};

template <typename T>
consteval auto get_member_reflections()
{
    constexpr auto ctx = std::meta::access_context::current();
    constexpr std::size_t count =
        std::meta::nonstatic_data_members_of(^^T, ctx).size();

    auto members =
        std::meta::nonstatic_data_members_of(^^T, ctx);

    std::array<std::meta::info, count> result{};

    for (std::size_t i = 0; i < count; ++i) {
        result[i] = members[i];
    }

    return result;
}

template <typename T>
T from_json(const JsonObject& json)
{
    T obj{};
    constexpr auto members = get_member_reflections<T>();

    template for (constexpr auto member : members)
    {
        constexpr auto name =
            std::meta::identifier_of(member);

        using FieldType =
            typename [: std::meta::type_of(member) :];

        if (!json.contains(name)) {
            continue;
        }

        if constexpr (is_optional<FieldType>::value)
        {
            using Inner =
                typename inner_type<FieldType>::type;

            obj.[: member :] = json.get<Inner>(name);
        }
        else if constexpr (is_vector<FieldType>::value) {
            using Inner =
                typename inner_type<FieldType>::type;

            // 这个示例处理“嵌套结构体数组”
            for (const auto& item : json.get_array(name)) {
                obj.[: member :].push_back(
                    from_json<Inner>(item)
                );
            }
        }
        else if constexpr (
            std::meta::is_class_type(^^FieldType) &&
            !std::is_same_v<FieldType, std::string>
        ) {
            obj.[: member :] =
                from_json<FieldType>(json.get_child(name));
        }
        else {
            obj.[: member :] =
                json.get<FieldType>(name);
        }
    }

    return obj;
}

struct Vector2 {
    float x;
    float y;
};

struct Enemy {
    std::string name;
    int health;
    std::optional<int> shield;
    std::vector<Vector2> patrol_path;
};

int main()
{
    JsonObject first_point;
    first_point.fields["x"] = 100.5f;
    first_point.fields["y"] = 200.0f;

    JsonObject second_point;
    second_point.fields["x"] = 150.0f;
    second_point.fields["y"] = 250.5f;

    JsonObject payload;
    payload.fields["name"] = std::string("General Radahn");
    payload.fields["health"] = 5000;
    payload.fields["shield"] = 150;
    payload.fields["patrol_path"] =
        std::vector<JsonObject>{
            first_point, second_point
        };

    Enemy enemy = from_json<Enemy>(payload);

    std::cout << "Name: " << enemy.name << '\n';
    std::cout << "Health: " << enemy.health << '\n';

    if (enemy.shield) {
        std::cout << "Shield: " << *enemy.shield << '\n';
    }

    std::cout << "Waypoints: "
              << enemy.patrol_path.size() << '\n';
    std::cout << "First waypoint: ("
              << enemy.patrol_path[0].x << ", "
              << enemy.patrol_path[0].y << ")\n";
}

在这个例子中,Enemy::shield 走 optional 分支,Enemy::patrol_path 走数组分支,而数组里的每个 Vector2 再由同一个 from_json 递归解析。结构体自身不需要继承某个基类,也不需要添加注册宏。

访问控制与错误处理

这里使用的是 std::meta::access_context::current(),所以成员查询遵循当前上下文的访问权限。示例中的字段都是公有成员。如果使用 unchecked() 查询私有成员,还需要认真考虑哪些数据允许被反序列化,以及是否应该暴露这些字段。

缺失字段目前会保留 T{} 初始化后的值。这适合演示,但实际项目往往需要区分“允许缺失的字段”和“必须存在的字段”。此外,std::any_cast 在类型不匹配时会抛出异常;真实 JSON 库还需要处理数字范围、null、数组元素错误等情况。

这个示例的 vector 分支只处理嵌套结构体数组,例如 std::vector<Vector2>。如果新增 std::vector<std::string>,还需要增加读取基础类型数组的分支,不能直接套用当前的递归调用。

结语

这个例子最值得关注的地方,是 template for 与成员拼接的配合:编译器在编译期逐个展开结构体字段,而程序在运行时根据 JSON 键读取对应的值。新增一个当前解析逻辑支持的字段时,就不用再手动维护一份同名字段映射。

说白了,反射解决的是认识并访问 C++ 类型结构的问题;JSON 文本解析、类型转换和错误处理仍要由完整的序列化库承担。把这两部分分清楚,才能看清静态反射真正省掉了哪些重复代码。

以上




上一篇:C++26 template for 异构遍历:用扩展语句遍历 tuple 与结构体
下一篇:MCU老手怎么切入端侧AI?TinyML从训练到部署学习路线详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-11 19:32 , Processed in 0.072541 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表