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 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
# Custom Serialization/Deserialization
See [Custom Read/Write](https://github.com/stephenberry/glaze/tree/main#custom-readwrite) in the main README.md for using `glz::custom`.
Advanced custom serialization/deserialization is achievable by implementing your own `from` and `to` specializations within the `glz` namespace.
> [!NOTE]
>
> Glaze provides `parse` and `serialize` helpers that decode the type of the value intended for `from` or `to` specializations. The developer only needs to specialize `from/to` structs, but you can use `parse/serialize` for cleaner syntax (see examples or Glaze's codebase).
Example:
```c++
struct date
{
uint64_t data;
std::string human_readable;
};
template <>
struct glz::meta<date>
{
using T = date;
static constexpr auto value = object("date", &T::human_readable);
};
namespace glz
{
template <>
struct from<JSON, date>
{
template <auto Opts>
static void op(date& value, is_context auto&& ctx, auto&& it, auto&& end)
{
parse<JSON>::op<Opts>(value.human_readable, ctx, it, end);
value.data = std::stoi(value.human_readable);
}
};
template <>
struct to<JSON, date>
{
template <auto Opts>
static void op(date& value, is_context auto&& ctx, auto&& b, auto&& ix) noexcept
{
value.human_readable = std::to_string(value.data);
serialize<JSON>::op<Opts>(value.human_readable, ctx, b, ix);
}
};
}
void example() {
date d{};
d.data = 55;
std::string s{};
expect(not glz::write_json(d, s));
expect(s == R"("55")");
d.data = 0;
expect(not glz::read_json(d, s));
expect(d.data == 55);
}
```
Notes:
The templated `Opts` parameter contains the compile time options.
For reading (`from` specializations), the parameters are:
- `value`: The object being parsed into
- `ctx`: The context containing runtime options (use `is_context auto&&`)
- `it`: Iterator to the current position in the input buffer
- `end`: Iterator to the end of the input buffer
For writing (`to` specializations), the parameters are:
- `value`: The object being serialized
- `ctx`: The context containing runtime options (use `is_context auto&&`)
- `b`: The output buffer to write to
- `ix`: The current index in the output buffer
## Bitfields
C++ bitfields cannot be referenced with a pointer-to-member, so they need `glz::custom` adapters in the `glz::meta` definition. The adapter lets you expose the bitfield as a regular integer while preserving the in-class bit packing.
```c++
struct bitfield_struct_t {
uint8_t f1 : 4{};
uint8_t f2 : 4{};
uint8_t f3{};
};
template <>
struct glz::meta<bitfield_struct_t>
{
using T = bitfield_struct_t;
static constexpr auto read_f1 = [](T& self, uint8_t v) { self.f1 = v; };
static constexpr auto write_f1 = [](const T& self) { return static_cast<uint8_t>(self.f1); };
static constexpr auto read_f2 = [](T& self, uint8_t v) { self.f2 = v; };
static constexpr auto write_f2 = [](const T& self) { return static_cast<uint8_t>(self.f2); };
static constexpr auto value = object(
"f1", glz::custom<read_f1, write_f1>,
"f2", glz::custom<read_f2, write_f2>,
"f3", &T::f3
);
};
```
- Ordinary members such as `f3` can continue to use direct pointers-to-members alongside the custom fields.
## UUID Example
Say we have a UUID library for converting a `uuid_t` from a `std::string_view` and to a `std::string`.
```c++
namespace glz
{
template <>
struct from<JSON, uuid_t>
{
template <auto Opts>
static void op(uuid_t& uuid, is_context auto&& ctx, auto&& it, auto&& end)
{
// Initialize a string_view with the appropriately lengthed buffer
// Alternatively, use a std::string for any size (but this will allocate)
std::string_view str = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
parse<JSON>::op<Opts>(str, ctx, it, end);
uuid = uuid_lib::uuid_from_string_view(str);
}
};
template <>
struct to<JSON, uuid_t>
{
template <auto Opts>
static void op(const uuid_t& uuid, is_context auto&& ctx, auto&& b, auto&& ix) noexcept
{
std::string str = uuid_lib::uuid_to_string(uuid);
serialize<JSON>::op<Opts>(str, ctx, b, ix);
}
};
}
```
# Handling Ambiguous Partial Specialization
You may want to custom parse a class that matches an underlying glaze partial specialization for a template like below:
```c++
template <class T> requires readable_array_t<T>
struct from<JSON, T>
```
If your own parsing function desires partial template specialization, then ambiguity may occur:
```c++
template <class T> requires std::derived_from<T, vec_t>
struct from<JSON, T>
```
To solve this problem, glaze will check for `custom_read` or `custom_write` values within `glz::meta` and remove the ambiguity and use the custom parser.
```c++
template <class T> requires std::derived_from<T, vec_t>
struct glz::meta<T>
{
static constexpr auto custom_read = true;
};
```
# Mimicking Standard Types
When a custom type serializes as a standard type (like `std::string`), you can declare this relationship using `mimic`. This is particularly useful when using custom types as **map keys**, where Glaze needs to know that the type behaves like a string to avoid double-quoting.
## The Problem
Without `mimic`, custom types used as map keys get wrapped in an extra quoting layer:
```c++
struct my_key
{
std::string value{};
auto operator<=>(const my_key&) const = default;
};
template <>
struct glz::meta<my_key>
{
static constexpr auto value = &my_key::value;
};
std::map<my_key, int> m{{{"hello"}, 42}};
// Produces: {"\"hello\"":42} -- double-quoted!
```
## The Solution
Add `using mimic = std::string;` to indicate that your type serializes as a string:
```c++
struct my_key
{
std::string value{};
auto operator<=>(const my_key&) const = default;
};
template <>
struct glz::meta<my_key>
{
using mimic = std::string;
static constexpr auto value = &my_key::value;
};
std::map<my_key, int> m{{{"hello"}, 42}};
// Produces: {"hello":42} -- correct!
```
## Available Concepts
Glaze provides concepts to check mimic relationships:
- `glz::has_mimic<T>` – checks if `T` has a `mimic` type defined
- `glz::mimic_type<T>` – extracts the mimic type from `T`
- `glz::mimics<T, Target>` – checks if `T` mimics exactly `Target`
- `glz::mimics_str_t<T>` – checks if `T` mimics any string type (satisfies `str_t`)
```c++
static_assert(glz::has_mimic<my_key>);
static_assert(glz::mimics<my_key, std::string>);
static_assert(glz::mimics_str_t<my_key>);
```
## When to Use
Use `mimic` when:
1. Your custom type serializes directly as a standard type (string, number, etc.)
2. You want to use it as a **map key** without double-quoting
3. You want compile-time documentation of what JSON type your custom type represents
|