简体   繁体   中英

How to use cereal to serialize enum types?

For example

enum Color {RED, BLUE, YELLOW};

And I want to serialize the type with cereal

Color c = RED;
JSONOutputArchive archive(std::cout);
archive(c);

Output looks like

"value0" : "RED"

or

"value0" : RED

Your desired output is something that no serialization library would be able to perform automatically, because the names you provide for the enum values are known only to the compiler.

Generally in this type of situation you need to provide code that can translate between a string representation and the numerical value for the enum (eg, Enum to String C++ ), or use a library that does this automatically for you.

Let's assume you have this functionality. You would then need to write ta pair of minimal serialization functions specialized for your enum.

Here is a fully working example. How you choose to go from enum to string is up to you, I went with two maps because it required no real effort on my part, but you could easily hide all of this behind a macro that would generate all of the required code for you:

#include <cereal/archives/json.hpp>
#include <iostream>
#include <sstream>
#include <map>

enum Color {RED, BLUE, GREEN};
enum AnotherEnum {HELLO_WORLD};

std::map<Color, std::string> ColorMapForward = {{RED, "RED"}, {BLUE, "BLUE"}, {GREEN, "GREEN"}};
std::map<std::string, Color> ColorMapReverse = {{"RED", RED}, {"BLUE", BLUE}, {"GREEN", GREEN}};

std::string Color_tostring( Color c )
{
  return ColorMapForward[c];
}

Color Color_fromstring( std::string const & s )
{
  return ColorMapReverse[s];
}

namespace cereal
{
  template <class Archive> inline
  std::string save_minimal( Archive const &, Color const & t )
  {
    return Color_tostring( t );
  }

  template <class Archive> inline
  void load_minimal( Archive const &, Color & t, std::string const & value )
  {
    t = Color_fromstring( value );
  }
}

int main()
{
  std::stringstream ss;

  {
    cereal::JSONOutputArchive ar(ss);
    ar( RED );
    ar( BLUE );
    ar( GREEN );
    ar( HELLO_WORLD ); // uses standard cereal implementation
  }

  std::cout << ss.str() << std::endl;
  std::stringstream ss2;

  {
    cereal::JSONInputArchive ar(ss);
    cereal::JSONOutputArchive ar2(ss2);

    Color r, b, g;
    AnotherEnum a;

    ar( r, b, g, a );
    ar2( r, b, g, a );
  }

  std::cout << ss2.str() << std::endl;
}

Gives as output:

{
    "value0": "RED",
    "value1": "BLUE",
    "value2": "GREEN",
    "value3": 0
}
{
    "value0": "RED",
    "value1": "BLUE",
    "value2": "GREEN",
    "value3": 0
}

You can cast the enum to an int and back, like this:

struct Camera {
    enum Mode { MODE_ORTHOGRAPHIC, MODE_PERSPECTIVE, MODE_COUNT };

    template<class Archive>
    void serialize( Archive &archive )
    {
        auto mode = static_cast<int>( mMode );    
        archive( cereal::make_nvp( "mode", mode ) );    
        mMode = static_cast<Mode>( mode );
    }

    Mode mMode = MODE_PERSPECTIVE;
};

I recommend combining cereal with magic_enum ( https://github.com/Neargye/magic_enum ). It uses compiler-specific code to get the job done but works with MSVC and GCC (also on the macOS version of it).

In my case, I created the following code which replaces the enum conversion code with magic_enum:

template <class Archive,
        cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value>
        = cereal::traits::sfinae, class T>
std::enable_if_t<std::is_enum_v<T>, std::string> save_minimal( Archive &, const T& h )
{
    return std::string(magic_enum::enum_name(h));
}

template <class Archive, cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value>
        = cereal::traits::sfinae, class T> std::enable_if_t<std::is_enum_v<T>, void> load_minimal( Archive const &, T& enumType, std::string const& str)
{
    enumType = magic_enum::enum_cast<T>(str).value();
}

Insert this and from now on all enum s are converted, it works like a charm. magic_enum lib itself is header-only, so very easy to integrate.

However, I just assume that enum_cast returns a valid value, you might add some checking there, throw an exception, etc.

Anyways, in this way, you can just serialise any of your structures that might contain enum s or enum class es and it will just work.

However, you need to make sure that these templates are within the namespace of your enum s. So, some duplicated code is maybe necessary if you want to serialise enum s of various namespaces (or you put it into a header and include that header into your namespaces, anyways).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM