libdisplaydevice master
C++ library to modify display devices.
json_serializer_details.h
Go to the documentation of this file.
1
5#pragma once
6
7#ifdef DD_JSON_DETAIL
8 // system includes
9 #include <algorithm>
10 #include <nlohmann/json.hpp>
11 #include <stdexcept>
12
13 // Special versions of the NLOHMANN definitions to remove the "m_" prefix in string form ('cause I like it that way ;P)
14 #define DD_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.m_##v1;
15 #define DD_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.m_##v1);
16
17 // Coverage has trouble with inlined functions when they are included in different units,
18 // therefore the usual macro was split into declaration and definition
19 #define DD_JSON_DECLARE_SERIALIZE_TYPE(Type) \
20 void to_json(nlohmann::json &nlohmann_json_j, const Type &nlohmann_json_t); \
21 void from_json(const nlohmann::json &nlohmann_json_j, Type &nlohmann_json_t);
22
23 #define DD_JSON_DEFINE_SERIALIZE_STRUCT(Type, ...) \
24 void to_json(nlohmann::json &nlohmann_json_j, const Type &nlohmann_json_t) { \
25 NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(DD_JSON_TO, __VA_ARGS__)) \
26 } \
27\
28 void from_json(const nlohmann::json &nlohmann_json_j, Type &nlohmann_json_t) { \
29 NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(DD_JSON_FROM, __VA_ARGS__)) \
30 }
31
32 // Coverage has trouble with getEnumMap() function since it has a lot of "fallthrough"
33 // branches when creating a map, therefore the macro has baked in pattern to disable branch coverage
34 // in GCOVR
35 #define DD_JSON_DEFINE_SERIALIZE_ENUM_GCOVR_EXCL_BR_LINE(Type, ...) \
36 const std::map<Type, nlohmann::json> & \
37 getEnumMap(const Type &) { \
38 static_assert(std::is_enum<Type>::value, #Type " must be an enum!"); \
39 static const std::map<Type, nlohmann::json> map = __VA_ARGS__; \
40 return map; \
41 } \
42\
43 void to_json(nlohmann::json &nlohmann_json_j, const Type &nlohmann_json_t) { \
44 nlohmann_json_j = findInEnumMap<Type>(#Type " is missing enum mapping!", [nlohmann_json_t](const auto &pair) { \
45 return pair.first == nlohmann_json_t; \
46 })->second; \
47 } \
48\
49 void from_json(const nlohmann::json &nlohmann_json_j, Type &nlohmann_json_t) { \
50 nlohmann_json_t = findInEnumMap<Type>(#Type " is missing enum mapping!", [&nlohmann_json_j](const auto &pair) { \
51 return pair.second == nlohmann_json_j; \
52 })->first; \
53 }
54
55namespace display_device {
59 namespace detail {
60 template<class T>
61 struct JsonTypeName;
62
63 template<>
64 struct JsonTypeName<double> {
65 static constexpr std::string_view m_name {"double"};
66 };
67
68 template<>
69 struct JsonTypeName<Rational> {
70 static constexpr std::string_view m_name {"rational"};
71 };
72
73 template<class T, class... Ts>
74 bool variantFromJson(const nlohmann::json &nlohmann_json_j, std::variant<Ts...> &value) {
75 if (nlohmann_json_j.at("type").get<std::string_view>() != JsonTypeName<T>::m_name) {
76 return false;
77 }
78
79 value = nlohmann_json_j.at("value").get<T>();
80 return true;
81 }
82 } // namespace detail
83
84 // A shared function for enums to find values in the map. Extracted here for UTs + coverage
85 template<class T, class Predicate>
86 typename std::map<T, nlohmann::json>::const_iterator findInEnumMap(const char *error_msg, Predicate predicate) {
87 const auto &map {getEnumMap(T {})};
88 auto it {std::ranges::find_if(map, predicate)};
89 if (it == std::end(map)) { // GCOVR_EXCL_BR_LINE for fallthrough branch
90 throw std::out_of_range(error_msg); // GCOVR_EXCL_BR_LINE for fallthrough branch
91 }
92 return it;
93 }
94} // namespace display_device
95
96namespace nlohmann {
97 // Specialization for optional types until they actually implement it.
98 template<class T>
99 struct adl_serializer<std::optional<T>> {
100 static void to_json(json &nlohmann_json_j, const std::optional<T> &nlohmann_json_t) {
101 if (nlohmann_json_t == std::nullopt) {
102 nlohmann_json_j = nullptr;
103 } else {
104 nlohmann_json_j = *nlohmann_json_t;
105 }
106 }
107
108 static void from_json(const json &nlohmann_json_j, std::optional<T> &nlohmann_json_t) {
109 if (nlohmann_json_j.is_null()) {
110 nlohmann_json_t = std::nullopt;
111 } else {
112 nlohmann_json_t = nlohmann_json_j.get<T>();
113 }
114 }
115 };
116
117 // Specialization for variant type.
118 // See https://github.com/nlohmann/json/issues/1261#issuecomment-2048770747
119 template<typename... Ts>
120 struct adl_serializer<std::variant<Ts...>> {
121 static void to_json(json &nlohmann_json_j, const std::variant<Ts...> &nlohmann_json_t) {
122 std::visit(
123 [&nlohmann_json_j]<class T>(const T &value) {
124 nlohmann_json_j["type"] = display_device::detail::JsonTypeName<std::decay_t<T>>::m_name;
125 nlohmann_json_j["value"] = value;
126 },
127 nlohmann_json_t
128 );
129 }
130
131 static void from_json(const json &nlohmann_json_j, std::variant<Ts...> &nlohmann_json_t) {
132 // Call variant_from_json for all types, only one will succeed
133 const bool found {(display_device::detail::variantFromJson<Ts>(nlohmann_json_j, nlohmann_json_t) || ...)};
134 if (!found) {
135 const std::string error {"Could not parse variant from type " + nlohmann_json_j.at("type").get<std::string>() + "!"};
136 throw std::invalid_argument(error);
137 }
138 }
139 };
140
141 // Specialization for chrono duration.
142 template<class Rep, class Period>
143 struct adl_serializer<std::chrono::duration<Rep, Period>> {
144 using NanoRep = decltype(std::chrono::nanoseconds {}.count());
145 static_assert(std::numeric_limits<Rep>::max() <= std::numeric_limits<NanoRep>::max(), "Duration support above nanoseconds have not been tested/verified yet!");
146
147 static void to_json(json &nlohmann_json_j, const std::chrono::duration<Rep, Period> &nlohmann_json_t) {
148 nlohmann_json_j = nlohmann_json_t.count();
149 }
150
151 static void from_json(const json &nlohmann_json_j, std::chrono::duration<Rep, Period> &nlohmann_json_t) {
152 nlohmann_json_t = std::chrono::duration<Rep, Period> {nlohmann_json_j.get<Rep>()};
153 }
154 };
155} // namespace nlohmann
156#endif