File: modify-reflection.md

package info (click to toggle)
glaze 6.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 7,312 kB
  • sloc: cpp: 109,539; sh: 99; ansic: 26; makefile: 13
file content (162 lines) | stat: -rw-r--r-- 6,115 bytes parent folder | download | duplicates (2)
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
# Extending Pure Reflection with `modify`

Glaze lets you _augment_ pure reflection without replacing it. Specialize `glz::meta<T>` with a `static constexpr auto modify = glz::object(...);` to rename reflected members, add aliases, or append entirely new key-value pairs while the remaining members still come from pure reflection.

## When to reach for `modify`

| Scenario | Recommendation |
| --- | --- |
| Rename a couple of fields while keeping the rest of the struct on pure reflection | Use `modify` |
| Add alternate JSON field names/aliases without duplicating your struct definition | Use `modify` |
| Expose derived data or helper views alongside reflected members | Use `modify` |
| Fully control the serialized shape (omit members, reorder everything, mix formats) | Continue to use a full `value` specialization |

Using `modify` keeps the zero-maintenance benefits of pure reflection: if you add a new data member to an aggregate type it will still be serialized automatically unless you override it explicitly inside `modify`.

## Basic usage

```cpp
struct point
{
   double x{};
   double y{};
};

// Rename `x`, add an alias for `y`, and expose a derived magnitude key

template <> struct glz::meta<point>
{
   static constexpr auto modify = glz::object(
      "real", &point::x,              // rename default key x -> real
      "imag", &point::y,              // rename default key y -> imag
      "radius", [](auto& self) {      // append a computed key
         return std::hypot(self.x, self.y);
      }
   );
};
```

Serializing `point{3, 4}` produces:

```json
{"real":3,"imag":4,"radius":5}
```

Notice that the original member list still participates: only the names you override change. Any members you do not mention retain their automatically generated names and order.

## Override a few keys

Often you just need to tweak a couple of fields while the bulk of the struct continues to rely on pure reflection. You can still do that without touching the entire definition:

```cpp
struct server_status
{
   std::string name;          // pure reflection keeps this key
   std::string region;        // …and this one
   uint64_t active_sessions;  // and all the others
   std::optional<std::string> maintenance;
   double cpu_percent{};
};

template <> struct glz::meta<server_status>
{
   static constexpr auto modify = glz::object(
      "maintenance_alias", [](auto& self) -> auto& { return self.maintenance; },
      "cpuPercent", &server_status::cpu_percent
   );
};
```

Serialising a value keeps the original fields while appending the two tweaks:

```json
{
  "name": "edge-01",
  "region": "us-east",
  "active_sessions": 2412,
  "maintenance": "scheduled",
  "cpuPercent": 73.5,
  "maintenance_alias": "scheduled"
}
```

Only the keys you touched change (`maintenance_alias`, `cpuPercent`). Everything else—`name`, `region`, `active_sessions`, `maintenance`—comes straight from pure reflection, so adding or removing members later still “just works”.

## Renaming reflected members

Pass a string literal before the member pointer to override the emitted key:

```cpp
static constexpr auto modify = glz::object(
   "first_name", &person::first,
   "last_name", &person::last
);
```

On read, Glaze respects the new key names. The original names (`first`, `last`) are not accepted unless you also add explicit aliases.

## Adding aliases

Aliases are just lambdas (or other invocables) that forward to the desired field:

```cpp
static constexpr auto modify = glz::object(
   "id", &widget::identifier,
   "legacy_id", [](auto& self) -> auto& { return self.identifier; }
);
```

Both `"id"` and `"legacy_id"` will now read and write the same member. When reading, aliases win over the base name whenever both appear in the payload (the last encountered value is used).

## Extending with additional keys

If a key in `modify` does not correspond to a reflected member it is treated as an _extension_. This is perfect for exposing alternative views or derived data:

```cpp
static constexpr auto modify = glz::object(
   "values", &complex_modify::records,
   "summary", [](auto& self) {
      return std::make_pair("count", self.records.size()); // example of a helper view
   }
);
```

Extensions are emitted in addition to all purely reflected members. During parsing Glaze invokes the callable so you can populate internal fields however you like.

## Mixing containers and optionals

`modify` works seamlessly with nested containers and optional fields because it composes on top of the existing reflection logic:

```cpp
struct dashboard
{
   std::vector<entry> items;
   std::map<std::string, double> metrics;
   std::optional<int> flag;
};

template <> struct glz::meta<dashboard>
{
   static constexpr auto modify = glz::object(
      "items", &dashboard::items,
      "items_alias", [](auto& self) -> auto& { return self.items; },
      "stats", &dashboard::metrics,
      "flag_status", &dashboard::flag
   );
};
```

No additional boilerplate is required—Glaze reuses the existing container serializers for every member you expose.

## Guidelines and caveats

- `modify` is only considered for aggregate types that would otherwise satisfy `glz::reflectable<T>`.
- Lambdas or function objects used inside `modify` must return an lvalue reference to participate in assignment, or a value if they are write-only extras.
- If a callable entry does not reference an existing member you **must** provide a key string (member pointers infer their key automatically).
- `modify` complements other `glz::meta` hooks: `skip`, `requires_key`, `unknown_read`, etc. continue to work as before.
- You can still provide a full `value` specialization later; it will override pure reflection and `modify` entirely.

## Choosing between `modify` and `value`

- Reach for `modify` when you only need small mutations to the default reflected shape (rename a few fields, add derived values, expose aliases).
- Fall back to `value` when you want to **remove** members, when the output order is critical, or when you need to interleave unrelated data in the serialized object.