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