serialization – (de)serializing c++ object almost automagically


Here’s one way to solve the serialization problem in c++20, using a json library:

#include <functional>

#include <unordered_map>

#include "json.hpp"

namespace nlm = nlohmann;

class properties
{
  struct property_info
  {
    std::function<void(nlm::json const&)> deserializor;
    std::function<nlm::json()> serializor;
  };

  std::unordered_map<std::string_view, property_info> reg_;

public:    
  //
  nlm::json state() const;
  void state(nlm::json const&);

  //
  template <typename U, typename ...A>
  auto register_property(std::string_view const& k, U& v, A&& ...a)
  {
    static_assert(!(sizeof...(a) % 2));
    static_assert(!(std::is_const_v<U>));

    reg_.try_emplace(k,
      (&v)(nlm::json const& j){v = j.get<U>();},
      (&v){return nlm::json(v);}
    );

    if constexpr (sizeof...(a))
    {
      register_property(std::forward<A>(a)...);
    }

    return (this)(auto&& ...a)
      {
        return register_property(std::forward<decltype(a)>(a)...);
      };
  }

  auto get(std::string_view const& k)
  {
    return reg_.find(k)->second.serializor();
  }

  template <typename U>
  void set(std::string_view const& k, U&& v)
  {
    reg_.find(k)->second.deserializor(std::forward<U>(v));
  }
};

nlm::json properties::state() const
{
  nlm::json r(nlm::json::object());

  for (auto i(reg_.cbegin()), cend(reg_.cend()); cend != i; i = std::next(i))
  {
    r.emplace(i->first, i->second.serializor());
  }

  return r;
}

void properties::state(nlm::json const& e)
{
  assert(e.is_object());
  auto const cend(reg_.cend());

  for (auto i(e.cbegin()), ecend(e.cend()); ecend != i; i = std::next(i))
  {
    auto& key(i.key());

    if (auto const j(std::as_const(reg_).find(key)); cend != j)
    {
      j->second.deserializor(i.value());
    }
  }
}

Example:

int main()
{
  struct S: properties
  {
    bool b{};
    int i{};

    S()
    {
      register_property("b", b)("i", i);
    }
  } s;

  s.set("b", true);
  s.set("i", 11.1);

  std::cout << s.get("b") << std::endl;
  std::cout << s.state() << std::endl;
}

2 functors for (de)serializing are generated for each registered property. If state is requested or set, these are executed accordingly. Obvious improvements are certain checks, getters/setters, instead of references. I think this is a nice quick solution for simple cases.

https://wandbox.org/permlink/J0vdLOTp5x6xvF9s