File: custom-serialization.md

package info (click to toggle)
glaze 6.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,948 kB
  • sloc: cpp: 121,839; sh: 99; ansic: 26; makefile: 13
file content (241 lines) | stat: -rw-r--r-- 6,899 bytes parent folder | download
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