-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
serialize std::variant<...> #1261
Comments
This section from the README should help you. |
@MattiasEppler Do you need further assistance? |
Hi. I do it in this way
I thought maybe there is an easier solution. But if not you can close this issue. Thanks |
I'm afraid not - at least I am not aware of a generic way to collect all the possible values from a variant. |
You could do something like: namespace nlohmann {
template <typename ...Args>
struct adl_serializer<std::variant<Args...>> {
static void to_json(json& j, std::variant<Args...> const& v) {
std::visit([&](auto&& value) {
j = std::forward<decltype(value)>(value);
}, v);
}
};
} I don't think it's possible to provide a generic What I would do is setting the index in the json only when serializing an enclosing type: struct Example {
std::variant<int, std::string, float> var;
};
void to_json(json& j, Example const& e) {
j = {{"index", e.var.index()}, {"variant", e.var}};
}
void from_json(json const&j, Example& e) {
auto const index = j.at("index").get<int>();
switch (index) {
case 0:
e.var = j.get<int>(); break;
case 1:
e.var = j.get<std::string>(); break;
case 2:
e.var = j.get<float>(); break;
default: throw std::runtime_error{""};
}
} |
On a second thought, there might be a way to make it generic, I'll try it out and post the results. |
Here is a complete example with the generic variant implementation. Note that it is far from being efficient, it might be improved with something similar to #include <iostream>
#include <stdexcept>
#include <string>
#include <variant>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace detail
{
template <std::size_t N>
struct variant_switch
{
template <typename Variant>
void operator()(int index, json const &value, Variant &v) const
{
if (index == N)
v = value.get<std::variant_alternative_t<N, Variant>>();
else
variant_switch<N - 1>{}(index, value, v);
}
};
template <>
struct variant_switch<0>
{
template <typename Variant>
void operator()(int index, json const &value, Variant &v) const
{
if (index == 0)
v = value.get<std::variant_alternative_t<0, Variant>>();
else
{
throw std::runtime_error(
"while converting json to variant: invalid index");
}
}
};
}
namespace nlohmann
{
template <typename ...Args>
struct adl_serializer<std::variant<Args...>>
{
static void to_json(json& j, std::variant<Args...> const& v)
{
std::visit([&](auto&& value) {
j["index"] = v.index();
j["value"] = std::forward<decltype(value)>(value);
}, v);
}
static void from_json(json const& j, std::variant<Args...>& v)
{
auto const index = j.at("index").get<int>();
::detail::variant_switch<sizeof...(Args) - 1>{}(index, j.at("value"), v);
}
};
}
struct A
{
int val;
};
bool operator==(A const& lhs, A const& rhs) noexcept
{
return lhs.val == rhs.val;
}
bool operator!=(A const& lhs, A const& rhs) noexcept
{
return !(lhs == rhs);
}
void to_json(json& j, A a)
{
j["val"] = a.val;
}
void from_json(json const& j, A& a)
{
a.val = j.at("val").get<int>();
}
int main(int argc, char const *argv[])
{
std::variant<int, std::string, float, A> v(A{42});
nlohmann::json j(v);
auto v2 = j.get<decltype(v)>();
assert(v == v2);
std::cout << j.dump(2) << std::endl;
v = std::string("Hello");
j = v;
std::cout << j.dump(2) << std::endl;
// invalidate the index
j["index"] = 42;
try
{
auto _ = j.get<decltype(v)>();
}
catch (std::runtime_error const& e)
{
std::cout << "Caught exception: " << e.what() << std::endl;
}
} |
Oh.... Thanks a lot for spending your time. I will try it. |
@MattiasEppler Did you try? |
Hi. Oh sorry. Will this be added to nlohmann\json? |
I guess we could add it, I'd like users to be able to do the following: std::variant<int, float, MyType> v(2);
json j = v;
auto i = j.get<int>();
auto v2 = j.get<decltype(v)>(); For this to work, we need to add a library-reserved field in the variant's alternative serialization: template <typename ...Args>
void to_json(json& j, std::variant<Args...> const& v) {
std::visit([&](auto const& a) {
j = a;
}, v);
j["__nlohmann_variant_index"] = v.index();
}
template <typename ...Args>
void from_json(json const& j, std::variant<Args...>& v) {
auto const index = j.at("__nlohmann_variant_index").get<int>();
// recursive ugliness to get the correct index...
} |
In my case its not necessary to serialize the index. |
After giving it a bit of thought, I think we should stay away from supporting
I'd like to avoid such compile-time toggle to enable/disable some specific conversion as well. |
I agree with @theodelrieu. In a lot of cases, a variant is not the sole variable to be serialized, so type information may be derived implicitly from other members. @MattiasEppler Is it OK to close the issue? |
Hi. Yes its ok. |
Edit: answered below QUESTION: @theodelrieu @nlohmann after writing the What do you recommend as the cleanest way to use std::variant? (I should add that my ultimate goal is to be able to json-ify a Here is a small example (which I used on the latest Visual Studio 15.9)
error C2440: 'initializing': cannot convert from 'TestVariant' to 'nlohmann::basic_jsonstd::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::adl_serializer' error C2440: 'initializing': cannot convert from 'json' to 'std::variant<int64_t,std::string>' ANSWER: here is the glue needed:
|
I would advise putting the conversion code inside the specialization, not calling your free function in the global namespace. Otherwise, it seems correct. |
@nlohmann @theodelrieu Hi, nlohmann has not support C++17 Variant? Have any Variant examples? |
The alternative 'cereal" library has support for boost variant by adding a separate includes file for those that need it. |
Any update on supporting std::variant? |
Well, there is the code in #1261 (comment). Apart of this, I would be happy to see a PR! |
Also want to see a variant example here |
just a comment. JSON can have odd ways of expressing what type will be in a member. Take something similar to geojson where we have a type field that tells us what the data field is [{
"type": "point",
"data": [1.0, 2.0]
},
{
"type": "poly",
"data": [1.0,2.0,3.0,4.0]
}] They are the same in terms of JSON types, but when deserializing they could be in a c++ structure like
|
My take on this: https://www.kdab.com/jsonify-with-nlohmann-json/ I needed a way to serialize/deserialize variant without saving the type. // Try to set the value of type T into the variant data
// if it fails, do nothing
template <typename T, typename... Ts>
void variant_from_json(const nlohmann::json &j, std::variant<Ts...> &data)
{
try {
data = j.get<T>();
} catch (...) {
}
}
template <typename... Ts>
struct nlohmann::adl_serializer<std::variant<Ts...>>
{
static void to_json(nlohmann::json &j, const std::variant<Ts...> &data)
{
// Will call j = v automatically for the right type
std::visit(
[&j](const auto &v) {
j = v;
},
data);
}
static void from_json(const nlohmann::json &j, std::variant<Ts...> &data)
{
// Call variant_from_json for all types, only one will succeed
(variant_from_json<Ts>(j, data), ...);
}
}; It will guess the value based on the data. For example, a
|
Hi narnaud Can you give an example how to deserialize.
|
Sorry just want to ask a question. |
I made a mistake in my previous code, the |
@narnaud Error C2672: 'nlohmann::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::adl_serializer,std::vector<uint8_t,std::allocator<_Ty>>>::get': no matching overloaded function found (346) on line
|
@MattiasEppler Sorry, my fault, I forget the namespace |
@narnaud |
sooooory. Just forgot to include the file where the adl_serializer is defined :-( |
Maybe somone an idea to an alternative of switch case. If i try to deserialize a variant with known an index not included in json
|
Why is this necessary? I got really stuck on this until I found this ANSWER... Why can every other type be converted with free to_json functions but it doesn't work with variants? |
Ah because it's in the std namespace... Thank you! |
This worked partially for me because I've been using
doesn't seem to throw even if T is not corresponding to the data in the json object. The logic is along the lines of using monostate as a fallback whenever the index doesn't match the variant type index, to avoid overwriting |
I found a little bit better solution for ser/deser of the
The first part is converting invariant's type to namespace utils
{
namespace details
{
template <typename T>
constexpr auto make_type_name() {
using namespace std::string_view_literals;
#ifdef __clang__
std::string_view name = __PRETTY_FUNCTION__;
name.remove_prefix("auto utils::details::make_type_name() [T = "sv.size());
name.remove_suffix("]"sv.size());
#endif
return name;
}
} // details
/**
* Convert type T into constexpr string.
*/
template<typename T>
constexpr auto type_name_sv = details::make_type_name<T>();
static_assert(type_name_sv<int> == std::string_view{"int"});
} // utils The second is serialization using pack foldind as proposed narnaud: namespace nlohmann
{
namespace
{
template <typename T, typename... Ts>
bool variant_from_json(const nlohmann::json& j, std::variant<Ts...>& data) {
if (j.at("type").get<std::string_view>() != utils::type_name_sv<T>)
return false;
data = j.at("data").get<T>();
return true;
}
}
template <typename... Ts>
struct adl_serializer<std::variant<Ts...>>
{
using Variant = std::variant<Ts...>;
using Json = nlohmann::json;
static void to_json(Json& j, const Variant& data) {
std::visit(
[&j](const auto& v) {
using T = std::decay_t<decltype(v)>;
j["type"] = utils::type_name_sv<T>;
j["data"] = v;
},
data);
}
static void from_json(const Json& j, Variant& data) {
// Call variant_from_json for all types, only one will succeed
bool found = (variant_from_json<Ts>(j, data) || ...);
if (!found)
throw std::bad_variant_access();
}
};
} // nlohmann This approach is linear in time without exceptions during testing variants. |
Hi.
I try to serialize std::variant<double,int> to json.
I look for an easy and comfortable way. Is there any solution available?
Or any idea how to do this.
The text was updated successfully, but these errors were encountered: