Skip to content

# Generic JSON

While Glaze is focused on strongly typed data, there is basic support for completely generic JSON.

If nothing is known about the JSON structure, then glz::generic may be helpful, but it comes at a performance cost due to the use of dynamic memory allocations. The previous `glz::json_t` name remains as an alias for backwards compatibility.

> Note: `glz::generic` preserves the insertion order of JSON keys. When you parse a JSON object and re-serialize it, the keys will appear in the same order as the original document. If you need lexicographically sorted keys, use the sorted aliases:

>     glz::generic_sorted json{};
>     glz::generic_sorted_i64 json_i64{};
>     glz::generic_sorted_u64 json_u64{};

## Generic Type Variants

Glaze provides insertion-order and sorted-key generic JSON types with different number storage strategies:

Type | Number Storage | Use Case
--- | --- | ---
`glz::generic` | `double` | Fast, JavaScript-compatible (default)
`glz::generic_i64` | `int64_t` then `double` | Signed integer precision
`glz::generic_u64` | `uint64_t` then `int64_t` then `double` | Full integer range
`glz::generic_sorted` | `double` | Lexicographically sorted object keys
`glz::generic_sorted_i64` | `int64_t` then `double` | Sorted keys + signed integer precision
`glz::generic_sorted_u64` | `uint64_t` then `int64_t` then `double` | Sorted keys + full integer range

### glz::generic (Default)

All numbers are stored as `double`. This is the fastest option and matches JavaScript's number semantics. However, integers larger than 2^53 will lose precision.

    glz::generic json{};
    std::string buffer = R"([5,"Hello World",{"pi":3.14}])";
    glz::read_json(json, buffer);
    assert(json[0].get<double>() == 5.0);
    assert(json[1].get<std::string>() == "Hello World");
    assert(json[2]["pi"].get<double>() == 3.14);
    assert(json[2]["pi"].as<int>() == 3);

### glz::generic_i64

Integers are stored as `int64_t`, with fallback to `double` for floating-point numbers. This preserves precision for signed integers up to 9.2 quintillion (2^63-1).

    glz::generic_i64 json{};
    std::string buffer = R"({"id": 9007199254740993, "value": 3.14})";
    glz::read_json(json, buffer);

    // Large integer preserved exactly (would lose precision in double)
    assert(json["id"].is_int64());
    assert(json["id"].get<int64_t>() == 9007199254740993LL);

    // Floating-point stored as double
    assert(json["value"].is_double());
    assert(json["value"].get<double>() == 3.14);

    // Use as<T>() for conversions
    assert(json["id"].as<double>() == 9007199254740993.0);

### glz::generic_u64

Provides full integer range support. Positive integers are stored as `uint64_t`, negative integers as `int64_t`, and floating-point numbers as `double`. This handles the complete unsigned 64-bit range (0 to 18.4 quintillion).

    glz::generic_u64 json{};
    std::string buffer = R"({"big_id": 18446744073709551615, "neg": -100, "pi": 3.14})";
    glz::read_json(json, buffer);

    // Maximum uint64_t value preserved exactly
    assert(json["big_id"].is_uint64());
    assert(json["big_id"].get<uint64_t>() == 18446744073709551615ULL);

    // Negative integers stored as int64_t
    assert(json["neg"].is_int64());
    assert(json["neg"].get<int64_t>() == -100);

    // Floating-point stored as double
    assert(json["pi"].is_double());
    assert(json["pi"].get<double>() == 3.14);

### Choosing the Right Type

- `glz::generic`: Use when performance matters most and you don't need large integer precision, or when interoperating with JavaScript.
- `glz::generic_i64`: Use when dealing with signed 64-bit IDs, timestamps, or APIs that return large signed integers.
- `glz::generic_u64`: Use when handling database IDs, blockchain values, or any unsigned 64-bit integers.
- `glz::generic_sorted*`: Use when you specifically need lexicographically sorted object keys for deterministic output compatibility.

## Basic Usage

    glz::generic json{};
    std::string buffer = R"([5,"Hello World",{"pi":3.14}])";
    glz::read_json(json, buffer);
    assert(json[0].get<double>() == 5.0);
    assert(json[1].get<std::string>() == "Hello World");
    assert(json[2]["pi"].get<double>() == 3.14);
    assert(json[2]["pi"].as<int>() == 3);

    glz::generic json = {
       {"pi", 3.141},
       {"happy", true},
       {"name", "Stephen"},
       {"nothing", nullptr},
       {"answer", {{"everything", 42.0}}},
       {"list", {1.0, 0.0, 2.0}},
       {"object", {
          {"currency", "USD"},
          {"value", 42.99}
       }}
    };
    std::string buffer{};
    glz::write_json(json, buffer);
    expect(buffer == R"({"pi":3.141,"happy":true,"name":"Stephen","nothing":null,"answer":{"everything":42},"list":[1,0,2],"object":{"currency":"USD","value":42.99}})");

## get() vs as()

All generic types are variants underneath. The `get<T>()` method mimics a `std::get` call for a variant, which rejects conversions and throws if the type doesn't match. The `as<T>()` method performs conversions.

    glz::generic json{};
    glz::read_json(json, "3.14");

    json.get<double>();  // 3.14 - exact type match
    json.as<int>();      // 3 - converts double to int

For `generic_i64` and `generic_u64`, `as<T>()` handles conversions from integer storage:

    glz::generic_i64 json{};
    glz::read_json(json, "42");

    json.get<int64_t>();  // 42 - stored as int64_t
    json.as<double>();    // 42.0 - converts to double
    json.as<int>();       // 42 - converts to int

## Type Checking generic

All generic types have member functions to check the JSON type:

- `.is_object()`
- `.is_array()`
- `.is_string()`
- `.is_number()` - returns true for any numeric type
- `.is_boolean()`
- `.is_null()`

There are also free functions of these, such as `glz::is_object(...)`

### Additional Type Checks for Integer Modes

`glz::generic_i64` and `glz::generic_u64` provide additional methods:

- `.is_int64()` - true if stored as `int64_t`
- `.is_double()` - true if stored as `double`
- `.is_uint64()` - true if stored as `uint64_t` (only `generic_u64`)
- `.as_number()` - returns the value as `double`, converting if necessary

    glz::generic_i64 json{};
    glz::read_json(json, "42");

    json.is_number();  // true - it's some kind of number
    json.is_int64();   // true - specifically stored as int64_t
    json.is_double();  // false - not stored as double

    double val = json.as_number();  // 42.0 - converts int64_t to double

## .empty()

Calling `.empty()` on a `generic` value will return true if it contains an empty object, array, or string, or a null value. Otherwise, returns false.

## .size()

Calling `.size()` on a `generic` value will return the number of items in an object or array, or the size of a string. Otherwise, returns zero.

## .dump()

Calling `.dump()` on a `generic` value is equivalent to calling `glz::write_json(value)`, which returns an `expected<std::string, glz::error_ctx>`.

## glz::raw_json

There are times when you want to parse JSON into a C++ string, to inspect or decode at a later point. `glz::raw_json` is a simple wrapper around a `std::string` that will decode and encode JSON without needing a concrete structure.

    std::vector<glz::raw_json> v{"0", "1", "2"};
    std::string s;
    glz::write_json(v, s);
    expect(s == R"([0,1,2])");

## Using `generic` As The Source

After parsing into a `generic` it is sometimes desirable to parse into a concrete struct or a portion of the `generic` into a struct. Glaze allows a `generic` value to be used as the source where a buffer would normally be passed.

    auto json = glz::read_json<glz::generic>(R"({"foo":"bar"})");
    expect(json->contains("foo"));
    auto obj = glz::read_json<std::map<std::string, std::string>>(json.value());
    // This reads the generic value into a std::map

Performance Note: When reading primitives or containers (vectors, maps, arrays, etc.) from `generic`, Glaze uses an optimized direct-traversal path that avoids JSON serialization. For complex user-defined structs, it falls back to JSON round-trip to handle reflection metadata.

Another example:

    glz::generic json{};
    expect(not glz::read_json(json, R"("Beautiful beginning")"));
    std::string v{};
    expect(not glz::read<glz::opts{}>(v, json));
    expect(v == "Beautiful beginning");

### Optimized Conversion from `generic`

When reading from a `generic` into primitives or containers, `glz::read_json` automatically uses an optimized direct-traversal path:

    glz::generic json{};
    glz::read_json(json, R"([1, 2, 3, 4, 5])");

    // Efficient direct conversion - no JSON serialization overhead
    std::vector<int> vec;
    auto ec = glz::read_json(vec, json);
    if (!ec) {
      // vec now contains {1, 2, 3, 4, 5}
    }

The optimization automatically applies to: - Primitives: `bool`, `double`, `int`, and other numeric types - Strings: `std::string` - Arrays: `std::vector`, `std::array`, `std::deque`, `std::list` - Maps: `std::map`, `std::unordered_map` - Nested combinations: `std::vector<std::vector<int>>`, `std::map<std::string, std::vector<double>>`, etc.

Benefits of the optimized path: - No JSON round-trip: Converts directly from the internal `generic` representation - Memory reuse: Existing allocations in the target container are preserved - Recursive efficiency: Nested containers are converted with zero intermediate serialization

For complex user-defined structs, `glz::read_json` automatically falls back to JSON round-trip to handle reflection metadata.

Advanced: If you need explicit control over the conversion process, `glz::convert_from_generic(result, source)` is available as a lower-level API that returns `error_ctx` instead of using the `expected` wrapper.

## Extracting Containers with JSON Pointers

`glz::get` can be used with JSON Pointers to extract values from a `generic` object. For primitive types (bool, double, std::string), `glz::get` returns a reference wrapper to the value stored in the `generic`:

    glz::generic json{};
    std::string buffer = R"({"name": "Alice", "age": 30, "active": true})";
    glz::read_json(json, buffer);

    // Get primitive types - returns expected<reference_wrapper<T>, error_ctx>
    auto name = glz::get<std::string>(json, "/name");
    if (name) {
      expect(name->get() == "Alice");
    }

    auto age = glz::get<double>(json, "/age");
    if (age) {
      expect(age->get() == 30.0);
    }

For container types (vectors, maps, arrays, lists, etc.), `glz::get` deserializes the value and returns a copy:

    glz::generic json{};
    std::string buffer = R"({
      "names": ["Alice", "Bob", "Charlie"],
      "scores": {"math": 95, "english": 87}
    })";
    glz::read_json(json, buffer);

    // Get container types - returns expected<T, error_ctx>
    auto names = glz::get<std::vector<std::string>>(json, "/names");
    if (names) {
      expect(names->size() == 3);
      expect((*names)[0] == "Alice");
    }

    auto scores = glz::get<std::map<std::string, int>>(json, "/scores");
    if (scores) {
      expect(scores->at("math") == 95);
    }

    // Works with other container types too
    auto names_list = glz::get<std::list<std::string>>(json, "/names");
    auto names_array = glz::get<std::array<std::string, 3>>(json, "/names");

This works because `glz::generic` stores arrays as `std::vector<glz::generic>` and objects as `glz::ordered_small_map<glz::generic>`. When you request a specific container type, Glaze deserializes the generic representation into your desired type.

## Compilation time optimization

`glaze/json/generic.hpp` is a heavyweight header: it pulls in the full JSON read/write machinery so that `glz::generic` values can be serialized, deserialized, navigated by JSON Pointer, and converted into arbitrary C++ types.

If `glz::generic` only appears in the signatures of a widely-included header (function parameters, return types, struct members) and the actual reading/writing happens in `.cpp` files, you can include the lightweight companion header instead:

    #include "glaze/json/generic_fwd.hpp"

This header provides the complete `glz::generic` / `glz::generic_json` type along with its inline accessors (`get`, `get_if`, `holds`, `operator[]`, `at`, `contains`, `is_*`, `size`, `empty`, `reset`, the in-place `operator=` overloads for scalars / strings / arrays / objects, etc.), the `glz::meta` specialization, and the type aliases (`glz::generic`, `glz::generic_i64`, `glz::generic_u64`, `glz::generic_sorted*`). That's enough to declare APIs and manipulate values in memory.

What requires the full `glaze/json/generic.hpp` (and produces a linker error if called without it):

- `generic.dump()` and any of the free `read_json` / `read` overloads that take a `generic_json`
- The templated `operator=(T&&)` for arbitrary user types (the one that round-trips through JSON)
- `glz::get<T>(generic, json_ptr)` for container types, `glz::navigate_to`, `glz::seek`, and `glz::convert_from_generic`

Typical pattern:

    // my_api.hpp - included widely; stays cheap to compile
    #include "glaze/json/generic_fwd.hpp"

    struct request {
       std::string method;
       glz::generic params;
    };

    void handle(const request& r);

    // my_api.cpp - the only place that needs read/write
    #include "glaze/json/generic.hpp"
    #include "my_api.hpp"

    void handle(const request& r) {
       auto serialized = r.params.dump();
       // ...
    }

This is the same split as `glaze/forward.hpp`, which provides lightweight forward declarations for `glz::meta`, `glz::to`, `glz::from`, and `glz::custom_t`.

## See Also

- Lazy JSON - On-demand parsing for selective field access (faster for extracting few fields)
- JSON Patch (RFC 6902) - Apply structured patches to `glz::generic` documents
- JSON Merge Patch (RFC 7386) - Apply partial updates to `glz::generic` documents
- JSON Pointer Syntax - Path syntax for navigating JSON documents
- Ordered Maps - Details on `glz::ordered_small_map` and `glz::ordered_map`