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.
|