Skip to content

Deserialising std::map Enum Keys #661

@stellarpower

Description

@stellarpower

std::map from an enum key to a value T is a setup I often use, it's important as it ensures our keys are well-defined and that we handle them (all, potentially) programatically. When deserialising we have the benefit of blowing up and failing early, rather than trying to convert from strings later and finsing something isn't handled.

On 0.24.0, the following does not parse for me:

using namespace std;

struct Test {

    enum class Colour { Red, Green, Blue };
    map<Colour, string> dictionary;

};

const char *testDocument = R".(
dictionary:
    Red  : Rojo
    Green: Verde
    Blue : Azul
).";

int main(int argc, char *argv[]){
    auto result = rfl::yaml::read<Test>(string(testDocument)).value();
}
 

reflect-cpp is falling back to treating "dicitonary" above as a sequence, as the key for our map is not a std::string.

It appears that the specialisations here form a kind of typemap specifying how a given member of the user-programmer's struct should be mapped from e.g. a YAML primitive (std::map <=> YAML Object in this case). These are specialised for map<std::string, ...> but not for any other type that is string-like (which I think would be a useful addition, there are third-party string libraries around, string_view, etc., which might have important user stories behind them), or, could be converted from a string as in this case with an enum. The tests and some of the bugs I see mention use of enums as values, but I think their use as keys (with the serialised format taking their string rather than their integral representation - there are valid cases for both and it would be good for the user-programmer to be able to choose between them depending on application) is an important one.

As as side note, I see in a few comments that one aim of reflect-cpp is to be production-grade in quality. Personally, files with zero meaningful comments in them, single-letter or unhelpful variable names (_r, obj, var - what variable, what object? A node from the serialised format? An "Object" in the JSON sense, or one from the wire format library? A member of the user-programmer's struct?), and multiple very similarly-named functions in the call stack (e.g. read) don't fit my definition of this. This took a lot longer (about a day) to debug than it should have, the code is very difficult to read and it is hard to follow what is happening or where I am from the call stack. I just make this point, as I like this package and want ot make more use of it, but like with any github project, my willingness to find time to fix issues, add features, and open an MR, or justify using it in a professional capacity and integrate into a project where clients will be paying for my time in fixing things when they go wrong inside the package - is inversely proportional to how much effort it takes just trying to decypher what is in front of me. Hence some time refactoring and at least renaming things and adding some inline documentation to make it easier for someone who isn't familiar with the internals to navigate what is happening, why, and how, would be very helpful.

So, I can only guess where the best place to add support or what the consequences would be - but to fix it for my limited usecase, I simply added a specialisation requiring is_enum_v for MapParser, added the same but negated to VectorParser to avoid the ambiguity in specialisations, and then an if constexpr handler for enums in to_pair to use the built-in string_to_enum

I also think that if possible (it might not be), it'd be nice to decouple these type-mappings from the reader and writer. R and W are template arguments, and not massively intrusive, but, it feels that the serialisation format should be separate from how we "broadcast" semantic types (mapping, sequence) into C++ types. And again, when they are all jammed into one function name, I can barely read the call stack because the current function name's flowing over multiple times my screen width. If I am right that this is functioning as a type-map, making it clearer and less noisy by pulling the reader and writer out elsewhere would facilitate support for user types, e.g., on of the many alternative hashmaps (folly, abseil, fixed-containers, robin, etc.) that are around.

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions