Skip to content

Commit

Permalink
json:variant supports creating types from field
Browse files Browse the repository at this point in the history
  • Loading branch information
fesily committed Nov 30, 2024
1 parent 46ab4d8 commit c914f57
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 5 deletions.
73 changes: 68 additions & 5 deletions iguana/json_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,6 @@ IGUANA_INLINE bool from_json_variant_impl(U &value, It it, It end, It &temp_it,
template <typename U, typename It, size_t... Idx>
IGUANA_INLINE void from_json_variant(U &value, It &it, It &end,
std::index_sequence<Idx...>) {
static_assert(!has_duplicate_type_v<std::remove_reference_t<U>>,
"don't allow same type in std::variant");
bool r = false;
It temp_it = it;
It temp_end = end;
Expand All @@ -549,11 +547,76 @@ IGUANA_INLINE void from_json_variant(U &value, It &it, It &end,
end = temp_end;
}

template <typename T, typename It>
IGUANA_INLINE bool try_read_variant_type(T& type_value, std::string_view target, It it, It end) {

skip_ws(it, end);
match<'{'>(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return false;
}

skip_ws(it, end);

std::string_view key = detail::get_key(it, end);
while (true) {
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Expected }"); }
using namespace detail;
skip_ws(it, end);
match<':'>(it, end);
if (key != target) {
// discard left
detail::skip_object_value(it, end);
}
else {
from_json_impl(type_value, it, end);
return true;
}

skip_ws(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return false;
}
else
IGUANA_LIKELY { match<','>(it, end); }
key = detail::get_key(it, end);
}

}

template <typename U, typename It>
IGUANA_INLINE void from_json_variant_by_type(U &value, It &it, It &end) {
using variant_t = std::remove_reference_t<U>;
const auto target = variant_type_field_name<variant_t>;
variant_type_field_t<variant_t> type;

if (!try_read_variant_type(type, target, it, end)) {
throw std::runtime_error ("variant expected type field: " + std::string{target});
}

variant_cast_helper<variant_t>{}(value, type);
std::visit([&](auto& val) {
from_json_impl(val, it, end);
}, value);
}

template <typename U, typename It, std::enable_if_t<variant_v<U>, int>>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
from_json_variant(value, it, end,
std::make_index_sequence<
std::variant_size_v<std::remove_reference_t<U>>>{});
using variant_t = std::remove_reference_t<U>;
static_assert(!has_duplicate_type_v<variant_t>,
"don't allow same type in std::variant");
if constexpr (has_variant_type_field_helper_v<variant_t>) {
from_json_variant_by_type(value, it, end);
} else {
from_json_variant(value, it, end,
std::make_index_sequence<
std::variant_size_v<variant_t>>{});
}
}
} // namespace detail

Expand Down
83 changes: 83 additions & 0 deletions iguana/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,89 @@ IGUANA_INLINE constexpr bool has_duplicate(const std::array<T, N>& arr) {
return false;
}

template <typename variant_t>
struct variant_type_field_helper : std::false_type {
void operator()(void*){};
};

template <size_t Idx, typename variant_t>
IGUANA_INLINE constexpr auto get_variant_type_field() {
using element_t = variant_element_t<Idx, variant_t>;
return variant_type_field_helper<variant_t>{}((element_t*)nullptr);
}

template <typename variant_t>
using variant_type_field_t = decltype(get_variant_type_field<0, variant_t>());

template <size_t Idx, typename variant_t>
constexpr inline auto variant_type_field_v = get_variant_type_field<Idx, variant_t>();

template <typename element_t, typename field_t>
IGUANA_INLINE constexpr size_t get_variant_element_field_index(field_t target_field) {
size_t find_index = size_t(-1);
ylt::reflection::for_each(element_t{}, [&](auto& field, auto name, auto index) {
if constexpr (std::is_same_v<ylt::reflection::remove_cvref_t<decltype(field)>, field_t>) {
if (field == target_field) {
find_index = index;
}
}
});
return find_index;
}

template <typename variant_t>
IGUANA_INLINE constexpr std::string_view get_variant_type_field_name() {
using element_t = variant_element_t<0, variant_t>;
constexpr size_t find_index = get_variant_element_field_index<element_t>(variant_type_field_v<0, variant_t>);
static_assert(find_index != size_t(-1));
return ylt::reflection::name_of<element_t, find_index>();
}

template <typename variant_t>
inline constexpr static std::string_view variant_type_field_name =
get_variant_type_field_name<variant_t>();

template <typename T>
struct variant_cast_helper {};

template <typename... Us>
struct variant_cast_helper<std::variant<Us...>> {
using variant_t = std::variant<Us...>;
using field_type = variant_type_field_t<variant_t>;

template <size_t Idx>
bool init_impl0(variant_t& v, field_type tt) {
constexpr field_type target_type = variant_type_field_v<Idx, variant_t>;
if (target_type == tt) {
v = variant_element_t<Idx, variant_t>{};
return true;
}
return false;
}

template <size_t... Idx>
void init_impl(variant_t& v, field_type tt, std::index_sequence<Idx...>) {
bool r = false;
((void)(!r && (r = init_impl0<Idx>(
v, tt),
true)),
...);
}

void operator()(variant_t& v, field_type tt) {
return init_impl(v, tt, std::make_index_sequence<
std::variant_size_v<variant_t>>{});
}
};

template <typename T>
struct has_variant_type_field_helper {
inline constexpr static bool value = variant_type_field_helper<T>::value;
};

template <typename T>
constexpr inline bool has_variant_type_field_helper_v = has_variant_type_field_helper<T>::value;

#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
template <typename... Types>
Expand Down
34 changes: 34 additions & 0 deletions test/test_some.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,40 @@ TEST_CASE("test variant") {
CHECK(v.var1[0] == v2.var1[0]);
}

struct my_variant_type0_t {
int value;
std::string_view tt = "1s";
};
YLT_REFL(my_variant_type0_t, value, tt);

struct my_variant_type1_t {
int value;
std::string_view tt = "2s";
};
YLT_REFL(my_variant_type1_t, value, tt);
namespace iguana {
template <>
struct variant_type_field_helper<std::variant<my_variant_type0_t, my_variant_type1_t>> : std::true_type {
template<typename T>
constexpr auto operator()(T*){
return T{}.tt;
};
};
}


TEST_CASE("test variant type") {
std::variant<my_variant_type0_t, my_variant_type1_t> var, var1;
var = my_variant_type0_t{66};
std::string s;
iguana::to_json(var, s);
std::cout << s << std::endl;

iguana::from_json(var1, s);
CHECK(std::holds_alternative<my_variant_type0_t>(var1));
CHECK(std::get<my_variant_type0_t>(var1).value == 66);
}

TEST_CASE("test from issues") {
test test1{};
std::string str1 =
Expand Down

0 comments on commit c914f57

Please sign in to comment.