#include #include #include #include #include #include using json = nlohmann::json; struct FieldOptions { double min = 0; double max = 0; bool hasMin = false; bool hasMax = false; bool hidden = false; bool transient = false; bool readOnly = false; std::string_view label; }; struct FieldOps { void (*save)(const void*, json&); void (*load)(void*, const json&); std::string (*show)(const void*); }; template struct Codec { static void Save(const void* value, json& out) { out = *static_cast(value); } static void Load(void* value, const json& in) { *static_cast(value) = in.get(); } static std::string Show(const void* value) { if constexpr (std::same_as) return *static_cast(value); else if constexpr (std::same_as) return *static_cast(value) ? "true" : "false"; else return std::to_string(*static_cast(value)); } static constexpr FieldOps ops{Save, Load, Show}; }; class FieldVisitor { public: template void Field(std::string_view key, T& value, FieldOptions options = {}) { Visit(key, &value, options, Codec::ops); } protected: virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) = 0; }; class JsonWriter final : public FieldVisitor { public: json Result() const { return result; } protected: void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { if (!options.transient) ops.save(value, result[std::string(key)]); } private: json result = json::object(); }; class JsonReader final : public FieldVisitor { public: explicit JsonReader(const json& source) : data(source) {} protected: void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { if (options.transient || !data.is_object()) return; const auto it = data.find(std::string(key)); if (it != data.end()) ops.load(value, *it); } private: const json& data; }; class ConsoleInspector final : public FieldVisitor { protected: void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { if (options.hidden) return; const auto label = options.label.empty() ? key : options.label; std::cout << label << " = " << ops.show(value); if (options.readOnly) std::cout << " [read only]"; if (options.hasMin || options.hasMax) { std::cout << " ["; if (options.hasMin) std::cout << options.min; std::cout << ", "; if (options.hasMax) std::cout << options.max; std::cout << "]"; } std::cout << '\n'; } }; struct Player { std::string name = "Ada"; int health = 100; float speed = 4.5f; bool active = true; std::string runtimeNote = "created at runtime"; void Reflect(FieldVisitor& visitor) { visitor.Field("name", name); visitor.Field("health", health, {.min = 0, .max = 100, .hasMin = true, .hasMax = true}); visitor.Field("speed", speed, {.min = 0, .hasMin = true, .label = "Move Speed"}); visitor.Field("active", active); visitor.Field("internalId", runtimeNote, {.hidden = true}); visitor.Field("runtimeNote", runtimeNote, {.transient = true}); } }; int main() { Player player; JsonWriter writer; player.Reflect(writer); const json saved = writer.Result(); std::cout << "Saved JSON\n" << saved.dump(2) << "\n\n"; player.health = 1; player.speed = 0; JsonReader reader(saved); player.Reflect(reader); std::cout << "Console inspector\n"; ConsoleInspector inspector; player.Reflect(inspector); }