Skip to content

# Pure Reflection

Glaze supports pure reflection for aggregate initializable structs in Clang, MSVC, and GCC.

> With C++26 compilers, Glaze can use P2996 Reflection as the underlying mechanism, removing the 128-member limit and providing cleaner implementation.

> Aggregate initializable means:

> - no user-declared constructors
> - no user-declared or inherited constructors
> - no private or protected direct non-static data members
> - no virtual base classes

There's no need to write any `glz::meta` structures or use any macros. The reflection is hidden from the user and computed at compile time. If you eventually need to rename just a couple of fields or add an alias while keeping everything else automatic, specialize `glz::meta<T>` with a `modify` object. The existing members stay under pure reflection; only the keys you mention are altered or appended.

Types that support pure reflection satisfy the `glz::reflectable<T>` concept. To check if any type (including those with `glz::meta` specializations) can use the reflection API, use the `glz::has_reflect<T>` concept.

- You can still write a full `glz::meta` to customize your serialization, which will override the default reflection entirely.

## Treating reflected structs as positional arrays

Some JSON use cases employ arrays even though the target type is a struct. Wrap a member with `glz::as_array<&T::member>` to map between the positional representation and your reflected struct.

    struct Person_details
    {
       std::string_view name;
       std::string_view surname;
       std::string_view city;
       std::string_view street;
    };

    struct Person
    {
       int id{};
       Person_details person{};
    };

    template <>
    struct glz::meta<Person>
    {
       using T = Person;
       static constexpr auto value = glz::object(
          "id", &T::id,
          "person", glz::as_array<&T::person> // consume a JSON array as Person_details
       );
    };

    std::string_view payload = R"({
       "id": 1,
       "person": ["Joe", "Doe", "London", "Chamber St"]
    })";

    Person p{};
    if (auto ec = glz::read_json(p, payload); ec) {
       throw std::runtime_error(glz::format_error(ec, payload));
    }

    auto out = glz::write_json(p).value();
    // out == R"({"id":1,"person":["Joe","Doe","London","Chamber St"]})"

`glz::as_array` relies on the wrapped type's reflection (pure reflection or an explicit `glz::meta`) to map each array element by index. Serialization uses that same order to emit an array, so you get positional reads and writes while the C++ type stays a struct. The wrapper works for JSON, BEVE, CSV, TOML, and any other Glaze format.

> CUSTOMIZATION NOTE:

> When writing custom serializers specializing `to<JSON>` or `from<JSON>`, your concepts for custom types might not take precedence over the reflection engine (when you haven't written a `glz::meta` for your type). The reflection engine tries to discern that no specialization occurs for your type, but this is not always possible.

> To solve this problem, you can add `static constexpr auto glaze_reflect = false;` to your class. Or, you can add a `glz::meta` for your type.

## Mixing glz::meta reflection with pure-reflected structs

If you have an struct with constructors you may need to use `glz::meta` to define the reflection. But, now your type might break the counting of the numbers of members when included in another struct.

To fix this, add a constructor for your struct to enable the proper counting: `my_struct(glz::make_reflectable) {}`

Example:

    struct V2
    {
       float x{};
       float y{};

       V2(glz::make_reflectable) {}
       V2() = default;

       V2(V2&&) = default;
       V2(const float* arr) : x(arr[0]), y(arr[1]) {}
    };

    template <>
    struct glz::meta<V2>
    {
       static constexpr auto value = object(&V2::x, &V2::y);
    };

    struct V2Wrapper
    {
       V2 x{}; // reflection wouldn't work except for adding `V2(glz::make_reflectable) {}`
    };