Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unordered_map (and std containers?) with enum class as key #241

Closed
Alexandre-Dhalenne opened this issue May 18, 2020 · 7 comments
Closed

Comments

@Alexandre-Dhalenne
Copy link

Hey,

I am trying to use your library to build a specific serialization.
Here is what I have :

template <typename E>
class TCriterion
{
public:
	TCriterion() = default;
	TCriterion(E iType, std::string iVal) : _type(iType), _value(iVal) {}

	const std::string& getValue() const {
		return _value;
	}

	const E getType() const {
		return _type;
	}

protected:
	JSONCONS_TYPE_TRAITS_FRIEND;
	E _type;
	std::string _value;
};

template <typename E>
class TCriteria
{
public:
	TCriteria() = default;

	const std::unordered_map <E, TCriterion<E>>& getCriteriaMap() const {
		return _criteriaMap;
	}

protected:
	JSONCONS_TYPE_TRAITS_FRIEND;
	std::unordered_map<E, TCriterion<E>, EnumClassHash> _criteriaMap;
};

So two templates for a Criterion and a Criteria.
But I specify them in a class :

class MyCriterion : public TCriterion<MyCriterionType>{
public:
	MyCriterion () = default;
	MyCriterion (MyCriterionType iType, std::string iVal) : TCriterion<MyCriterionType>(iType,iVal){}
private:
	JSONCONS_TYPE_TRAITS_FRIEND;
};

class MyCriteria : public TCriteria<MyCriterionType> {
public:
	MyCriteria () = default;
};

I use these macros :

JSONCONS_TPL_ALL_MEMBER_NAME_TRAITS(1,online::service::TCriterion, (_type,"name"), (_value,"value"))
JSONCONS_TPL_ALL_MEMBER_NAME_TRAITS(1, online::service::TCriteria, (_criteriaMap, "criteriaList"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(online::service::MyCriterion, (_type, "name"), (_value, "value"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(online::service::MyCriteria , (_criteriaMap, "criteriaList"))

I defined an enum class MyCriterionType like this :

	enum class MyCriterionType{
		First,
		Presentation = First,
		MessageType,
		Version,
		Release,
		Last
	};

	struct EnumClassHash {
		template<typename T>
		std::size_t operator()(T t) const{
			return static_cast<std::size_t>(t);
		}
	};//To be able to use the enum class as key

Everything works for MyCriterion. But When I try to decode MyCriteria, I have an error at compilation time :

invalid explicit template argument(s) for std::enable_if<jsoncons::is_json_type_traits_specialized<jsoncons::basic_json<char,jsoncons::sorted_policy,std::allocator<char>>,T,void>::value,T>::type jsoncons::basic_json<char,jsoncons::sorted_policy,std::allocator<char>>::as(void) const'	

After somme debugging, I found that it comes from the enum as key. When I replace it by int for instance, it works.
Everything is well declared and in order for jsoncons.
Should I specify my own type_traits for this case ?

@Alexandre-Dhalenne
Copy link
Author

After more exploration, I found a way to do it by redefining my own type_traits, but I am wondering if there is a way to access to the name of the enum stored in

struct json_type_traits<Json, EnumType>

After more investigation I would need only a

std::unordered_map <MyCriterionType, std::string>

instead of a

std::unordered_map <MyCriterionType, MyCriterion>

Here is the "raw" solution :

namespace jsoncons
{
	template <class Json>
	struct json_type_traits<Json, std::unordered_map <CriterionType, std::string>>
	{
		using allocator_type = typename Json::allocator_type;
		static bool is(const Json& val) noexcept
		{
			if (!val.is_object())
			{
				return false;
			}
			return true;

		}

		static std::unordered_map <CriterionType, std::string> as(const Json& j)
		{
			std::unordered_map <CriterionType, std::string> val;
			val.emplace(CriterionType::MessageType,  j.at("messageType").template as<std::string>());
			val.emplace(CriterionType::Presentation, j.at("presentation").template as<std::string>());
			return val;
		}

		static Json to_json(std::unordered_map <online::service::SI_CriterionType, std::string> val, allocator_type alloc=allocator_type())
		{
			Json j;
			j.try_emplace("messageType", val[CriterionType::MessageType]);
			j.try_emplace("presentation", val[CriterionType::Presentation]);
			return j;
		}
	};
}

As you can see it would be usefull to have access to enum-generated struct so I can loop on each pair of enumtype-name and be generic.

What do you think ?

@danielaparker
Copy link
Owner

Currently, the jsoncons provided traits for map only support strings and integers as keys. I'll look into generalizing that.

@Alexandre-Dhalenne
Copy link
Author

Okay thanks.
If you want, here is what I did for my specific case (if it can help anyone, you can replace CriterionType by any Enum declared with JSONCONS_ENUM_TRAITS and JSONCONS_ENUM_NAME_TRAITS).

namespace jsoncons
{
	template <class Json>
	struct json_type_traits<Json, std::unordered_map <CriterionType, std::string>>
	{
	
		using allocator_type = typename Json::allocator_type;
		static bool is(const Json& val) noexcept
		{
			if (!val.is_object())
			{
				return false;
			}

			return true;
		}

		static std::unordered_map <CriterionType, std::string> as(const Json& j)
		{
			std::unordered_map <CriterionType, std::string> val;
			auto [left, right] = json_type_traits<Json, CriterionType>::get_values();
			for (auto x = left; x != right; x += 1)
			{
				if (j.contains(x->second)){
				
					val.emplace(x->first, j.at(x->second).template as<std::string>());
				}
			}
			return val;
		}

		static Json to_json(std::unordered_map <CriterionType, std::string> val, allocator_type alloc=allocator_type())
		{
			Json j;
			auto [left,right] = json_type_traits<Json, CriterionType>::get_values();
			for (auto x = left; x != right; x+=1)
			{
				if (val.find(x->first) != val.end()) {
					j.try_emplace(x->second, val[x->first]);
				}
			}
			return j;
		}
	};
}

Thanks for your work !

@rbroggi
Copy link

rbroggi commented May 18, 2020

Nice job!

@danielaparker
Copy link
Owner

I've generalized the json_type_traits for maps such that it's supported for all key types and mapped types that themselves have json_type_traits defined, and included your example as a test case.

Enhancement is on master.

@Alexandre-Dhalenne
Copy link
Author

Thanks a lot !

@danielaparker
Copy link
Owner

Enhancement is in v0.152.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants