You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过C++模板实现JSON编解码器(codec)类型以简化遗留JSON解析器的使用

如何通过C++模板实现JSON编解码器(codec)类型以简化遗留JSON解析器的使用

设计合理性分析

你的这个设计思路非常合理,甚至可以说是针对这类重复编解码逻辑的最优解之一,原因如下:

  1. 消除样板代码:原有的encode/decode函数中重复的put/get调用被模板化封装,只需要一次定义类型与键的对应关系,避免了冗余代码。
  2. 提升可维护性:修改成员、键名或类型时,只需修改编解码器的定义,无需在两个函数中分别同步修改,大幅降低了漏改、错改的风险。
  3. 编译期安全检查:类型与键的绑定关系在编译期固定,静态断言可以提前发现类型/键数量不匹配的问题,避免运行时因拼写错误或类型不匹配导致的bug。

实现方案

根据你允许使用最新C标准的要求,以下提供几种适配不同场景的实现,其中C20版本最简洁,char键版本完全贴合你的伪代码思路。


方案1:C++20 简洁版(推荐)

利用C++20支持的std::string_view非类型模板参数,直接将JSON键作为字符串字面量传入模板,代码直观且扩展性强:

#include <tuple>
#include <string_view>
#include <utility>
#include <string>

// 假设遗留JSON API与你提供的一致
namespace json {
    struct json_value;
    json_value* new_object(size_t size);
    void put(json_value* obj, const char* key, bool value);
    void put(json_value* obj, const char* key, int value);
    void put(json_value* obj, const char* key, float value);
    void put(json_value* obj, const char* key, const std::string& value);
    
    void get(const json_value* obj, const char* key, bool& value);
    void get(const json_value* obj, const char* key, int& value);
    void get(const json_value* obj, const char* key, float& value);
    void get(const json_value* obj, const char* key, std::string& value);
}

template <typename... Ts, std::string_view... Keys>
struct JsonCodec {
    // 编译期检查:类型数量必须与键数量一致
    static_assert(sizeof...(Ts) == sizeof...(Keys), "类型数量必须与JSON键数量匹配");

    // 编码:接收对应类型的参数,生成JSON对象
    static json::json_value* encode(const Ts&... values) {
        constexpr size_t member_count = sizeof...(Ts);
        auto* obj = json::new_object(member_count);

        // 用索引序列遍历键与值的对应关系,展开put调用
        auto put_impl = [obj]<size_t... Is>(std::index_sequence<Is...>, const auto&... vs) {
            const auto* keys[] = {Keys.data()...};
            const auto values_tuple = std::make_tuple(vs...);
            (json::put(obj, keys[Is], std::get<Is>(values_tuple)), ...);
        };

        put_impl(std::index_sequence_for<Ts...>{}, values...);
        return obj;
    }

    // 解码方式1:返回tuple,配合C++17+结构化绑定使用
    static std::tuple<Ts...> decode(const json::json_value* obj) {
        std::tuple<Ts...> result;
        const auto* keys[] = {Keys.data()...};

        auto get_impl = [obj, keys]<size_t... Is>(std::index_sequence<Is...>) {
            (json::get(obj, keys[Is], std::get<Is>(result)), ...);
        };

        get_impl(std::index_sequence_for<Ts...>{});
        return result;
    }

    // 解码方式2:直接赋值给变量,与原代码用法更贴近
    template <typename... Vs>
    static void decode(const json::json_value* obj, Vs&... vars) {
        static_assert(sizeof...(Vs) == sizeof...(Ts), "变量数量必须与编解码器类型数量匹配");
        const auto* keys[] = {Keys.data()...};
        auto vars_tuple = std::tie(vars...);

        auto get_impl = [obj, keys]<size_t... Is>(std::index_sequence<Is...>) {
            (json::get(obj, keys[Is], std::get<Is>(vars_tuple)), ...);
        };

        get_impl(std::index_sequence_for<Ts...>{});
    }
};

方案2:贴近你伪代码的char键版本(C++17+)

如果你的JSON键确实是单个字符(如'b'、'i'),可以直接用char作为非类型模板参数,完全贴合你的伪代码思路:

template <typename... Ts, char... Keys>
struct JsonCodec {
    static_assert(sizeof...(Ts) == sizeof...(Keys), "类型数量必须与键数量匹配");

    static json::json_value* encode(const Ts&... values) {
        constexpr size_t member_count = sizeof...(Ts);
        auto* obj = json::new_object(member_count);
        // 转换单个char为C风格字符串(自动补'\0')
        const char* keys[] = {{Keys, '\0'}...};

        auto put_impl = [obj, keys]<size_t... Is>(std::index_sequence<Is...>, const auto&... vs) {
            auto values_tuple = std::make_tuple(vs...);
            (json::put(obj, keys[Is], std::get<Is>(values_tuple)), ...);
        };

        put_impl(std::index_sequence_for<Ts...>{}, values...);
        return obj;
    }

    // decode函数实现与方案1类似,仅键的生成方式不同
    static std::tuple<Ts...> decode(const json::json_value* obj) {
        std::tuple<Ts...> result;
        const char* keys[] = {{Keys, '\0'}...};

        auto get_impl = [obj, keys]<size_t... Is>(std::index_sequence<Is...>) {
            (json::get(obj, keys[Is], std::get<Is>(result)), ...);
        };

        get_impl(std::index_sequence_for<Ts...>{});
        return result;
    }
};

使用示例

方案1(C++20)的使用:

// 1. 定义编解码器:绑定类型与对应的JSON键
using FooCodec = JsonCodec<bool, int, float, std::string, "b", "i", "f", "s">;

// 2. 编码示例
bool b_val = true;
int i_val = 42;
float f_val = 3.14f;
std::string s_val = "hello";
json::json_value* encoded_obj = FooCodec::encode(b_val, i_val, f_val, s_val);

// 3. 解码示例:结构化绑定(C++17+)
auto [decoded_b, decoded_i, decoded_f, decoded_s] = FooCodec::decode(encoded_obj);

// 4. 解码示例:直接赋值给变量(兼容原有习惯)
bool new_b;
int new_i;
float new_f;
std::string new_s;
FooCodec::decode(encoded_obj, new_b, new_i, new_f, new_s);

方案2(char键版本)的使用:

// 完全贴合你伪代码的编解码器定义
using FooCodec = JsonCodec<bool, int, float, std::string, 'b', 'i', 'f', 's'>;

// 编码/解码用法与方案1完全一致
json::json_value* encoded_obj = FooCodec::encode(b_val, i_val, f_val, s_val);
auto [decoded_b, decoded_i, decoded_f, decoded_s] = FooCodec::decode(encoded_obj);

关键设计要点

  1. 可变模板参数:用模板参数包Ts...和Keys...接收类型与键的对应序列,保证扩展性。
  2. 索引序列与折叠表达式:利用std::index_sequence(C11+,C11需自行实现)和折叠表达式(C++17+)遍历参数包,实现批量put/get操作,避免手动展开。
  3. 编译期断言:通过static_assert提前检查类型/键数量匹配问题,将错误暴露在编译阶段。
  4. 灵活性兼容:提供两种解码方式,既支持现代C++的结构化绑定,也兼容原有直接赋值的习惯,降低迁移成本。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 12:08:06