Skip to content

# 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

    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:

    {"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:

    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:

    {
      "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:

    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:

    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:

    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:

    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.