1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
# Custom Wrappers
Custom wrappers are used to change the serialization/deserialization on basic types (e.g. int, double, std::string).
The example below shows how numbers can be read in via strings by providing a wrapper to indicate the proper serialization/deserialization.
```c++
template <class T>
struct quoted_helper
{
T& val;
};
namespace glz
{
template <class T>
struct from<JSON, quoted_helper<T>>
{
template <auto Opts>
static void op(auto&& value, auto&& ctx, auto&& it, auto&& end)
{
skip_ws<Opts>(ctx, it, end);
if (match<'"'>(ctx, it)) {
return;
}
parse<JSON>::op<Opts>(value.val, ctx, it, end);
if (match<'"'>(ctx, it)) {
return;
}
}
};
template <class T>
struct to<JSON, quoted_helper<T>>
{
template <auto Opts>
static void op(auto&& value, is_context auto&& ctx, auto&& b, auto&& ix) noexcept
{
dump<'"'>(b, ix);
serialize<JSON>::op<Opts>(value.val, ctx, b, ix);
dump<'"'>(b, ix);
}
};
}
template <auto MemPtr>
constexpr decltype(auto) qouted()
{
return [](auto&& val) { return quoted_helper<std::decay_t<decltype(val.*MemPtr)>>{val.*MemPtr}; };
}
struct A
{
double x;
};
template <>
struct glz::meta<A>
{
static constexpr auto value = object("x", qouted<&A::x>());
// or...
//static constexpr auto value = object("x", [](auto&& val) { return quoted_helper(val.x); });
};
void example() {
A a{3.14};
std::string buffer{};
expect(not glz::write_json(a, buffer));
expect(buffer == R"({"x":"3.14"})");
buffer = R"({"x":"999.2"})";
expect(not glz::read_json(a, buffer));
expect(a.x == 999.2);
}
```
## Custom Type Parsing
Custom wrappers allow you to define specialized serialization/deserialization behavior for specific types. This is useful when working with formats that differ from standard JSON representations.
### Example: Python-style Booleans
Some systems (like Python) represent booleans as `True`/`False` instead of JSON's standard `true`/`false`. Here's how to create a custom wrapper to handle this format:
#### Step 1: Define the Wrapper Helper
```c++
template <class T>
struct python_bool_helper
{
T& val; // Reference to the actual boolean value
};
```
#### Step 2: Specialize the Parser (`from`)
```c++
namespace glz
{
template <class T>
struct from<JSON, python_bool_helper<T>>
{
template <auto Opts>
static void op(auto&& value, auto&& ctx, auto&& it, auto&& end)
{
skip_ws<Opts>(ctx, it, end);
if (it >= end) {
ctx.error = error_code::unexpected_end;
return;
}
// Check for 'T' (True) or 'F' (False)
if (*it == 'T') {
if (it + 4 <= end && std::string_view(it, 4) == "True") {
value.val = true;
it += 4;
} else {
ctx.error = error_code::expected_true_or_false;
}
} else if (*it == 'F') {
if (it + 5 <= end && std::string_view(it, 5) == "False") {
value.val = false;
it += 5;
} else {
ctx.error = error_code::expected_true_or_false;
}
} else {
ctx.error = error_code::expected_true_or_false;
}
}
};
```
#### Step 3: Specialize the Serializer (`to`)
```c++
template <class T>
struct to<JSON, python_bool_helper<T>>
{
template <auto Opts, class B>
static void op(auto&& value, is_context auto&&, B&& b, auto&& ix) noexcept
{
if (value.val) {
std::memcpy(&b[ix], "True", 4);
ix += 4;
} else {
std::memcpy(&b[ix], "False", 5);
ix += 5;
}
}
};
}
```
#### Step 4: Create a Convenience Function
```c++
template <auto MemPtr>
constexpr decltype(auto) python_bool()
{
return [](auto&& val) {
return python_bool_helper<std::decay_t<decltype(val.*MemPtr)>>{val.*MemPtr};
};
}
```
#### Step 5: Use in Your Structs
```c++
struct ConfigData
{
std::string name;
int port;
bool debug_mode;
bool enable_logging;
};
template <>
struct glz::meta<ConfigData>
{
using T = ConfigData;
static constexpr auto value = object(
&T::name,
&T::port,
"debug_mode", python_bool<&T::debug_mode>(), // Custom wrapper for this field
"enable_logging", python_bool<&T::enable_logging>() // Custom wrapper for this field
);
};
```
#### Usage Example
```c++
ConfigData config{.name = "MyApp", .port = 8080, .debug_mode = true, .enable_logging = false};
// Writing produces: {"name":"MyApp","port":8080,"debug_mode":True,"enable_logging":False}
std::string json_output;
glz::write_json(config, json_output);
// Reading accepts Python-style booleans
std::string python_json = R"({"name":"TestApp","port":3000,"debug_mode":False,"enable_logging":True})";
ConfigData parsed_config;
glz::read_json(parsed_config, python_json);
```
### Alternative Approaches
1. **Custom Type Wrapper**: Create your own boolean type with `glz::to`/`glz::from` specializations, allowing pure reflection usage
2. **Global Override**: Override boolean parsing globally (affects all boolean fields)
The custom wrapper approach shown above provides the most flexibility and performance, allowing field-by-field control over serialization behavior.
|