C++17下用现代化通用可变参数宏简化JSON反序列化的方案问询
Awesome question—when you're dealing with hundreds of ECS components, repetitive from_json boilerplate is the last thing you need. Since you're using C++17, we can leverage variadic templates and modern language features to build a clean, scalable solution that’s almost as concise as the macro syntax you’re imagining. Let’s break down the best approaches:
Pure C++17 Solution (No Third-Party Libraries)
This approach uses custom templates and a minimal macro to eliminate repetitive code, sticking strictly to C++17 features.
Step 1: Build Variadic Deserialization Helpers
First, create helper functions to handle single and multiple member deserialization using C++17 fold expressions:
namespace JSON { // Deserialize a single struct member template <typename T, typename MemberType> void deserialize_single(T& obj, const Node& json, MemberType T::*member_ptr, const char* key_name) { json_get(obj.*member_ptr, key_name, json); } // Expand deserialization for all members via fold expression template <typename T, typename... Args> void deserialize_members(T& obj, const Node& json, Args&&... args) { (deserialize_single(obj, json, std::forward<Args>(args)), ...); } }
Step 2: Create the Concise Registration Macro
Wrap the helpers in a macro that matches the syntax you want. This macro takes your struct type and member names, then generates the full from_json function automatically:
// Helper to pair a member pointer with its stringified name #define _JSON_MEMBER_PAIR(Type, Member) &Type::Member, #Member // Boost.Preprocessor (optional but cleaner for arbitrary member counts) #include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/variadic/to_seq.hpp> #define _JSON_EXPAND_PAIR(r, Type, i, Member) , _JSON_MEMBER_PAIR(Type, Member) // Main macro: Use like JSON(MyStruct, propertyA, propertyB, propertyC) #define JSON(Type, ...) \ namespace JSON { \ void from_json(const Node& json, Type& obj) { \ deserialize_members(obj, json, \ _JSON_MEMBER_PAIR(Type, BOOST_PP_SEQ_HEAD(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))) \ BOOST_PP_SEQ_FOR_EACH_I(_JSON_EXPAND_PAIR, Type, BOOST_PP_SEQ_TAIL(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))) \ ); \ } \ } // If you can't use Boost, here's a simpler (limited) version for up to 5 members: // #define JSON(Type, M1, M2, M3...) \ // namespace JSON { \ // void from_json(const Node& json, Type& obj) { \ // deserialize_members(obj, json, \ // _JSON_MEMBER_PAIR(Type, M1), _JSON_MEMBER_PAIR(Type, M2), _JSON_MEMBER_PAIR(Type, M3) \ // ); \ // } \ // }
Usage Example
Register your component in one line:
struct MyStruct { int propertyA; std::string propertyB; std::vector<int> propertyC; }; // Generates the full from_json function automatically JSON(MyStruct, propertyA, propertyB, propertyC);
Even Cleaner: Boost.PFR (Compile-Time Reflection)
If you’re open to a lightweight Boost dependency, Boost.PFR (Plain Old Data Reflection) lets you eliminate macros entirely. It works for any aggregate type (public members, no custom constructors)—perfect for ECS components.
Setup
Include Boost.PFR and write a generic from_json template:
#include <boost/pfr.hpp> namespace JSON { // Generic from_json for all flat aggregate types template <typename T> std::enable_if_t<boost::pfr::is_flat_v<T>> from_json(const Node& json, T& obj) { // Iterate over all struct fields at compile time boost::pfr::for_each_field(obj, [&](auto& member, std::size_t idx) { // Get the member's name as a compile-time string const char* member_name = boost::pfr::get_name<idx, T>(); json_get(member, member_name, json); }); } }
Usage Example
No extra code needed—just define your struct:
struct MyStruct { int propertyA; std::string propertyB; std::vector<int> propertyC; }; // from_json is automatically generated for you!
Key Takeaways
- C++17 Features Used: Fold expressions, variadic templates, compile-time stringification, and (with Boost.PFR) compile-time reflection.
- ECS Scalability: Both solutions scale seamlessly for hundreds of components—no more copying and pasting
json_getlines. - Flexibility: The pure C++17 solution lets you override JSON keys (replace
#Memberwith a custom string), while Boost.PFR uses member names directly for zero-config deserialization.
内容的提问来源于stack exchange,提问作者kbirk

