Skip to content

# Custom Wrappers

Custom wrappers are used to change the serialization/deserialization on basic types (e.g. int, double, std::string).

The example below shows how numbers can be read in via strings by providing a wrapper to indicate the proper serialization/deserialization.

    template <class T>
    struct quoted_helper
    {
       T& val;
    };

    namespace glz
    {
       template <class T>
       struct from<JSON, quoted_helper<T>>
       {
          template <auto Opts>
          static void op(auto&& value, auto&& ctx, auto&& it, auto&& end)
          {
             skip_ws<Opts>(ctx, it, end);
             if (match<'"'>(ctx, it)) {
                return;
             }
             parse<JSON>::op<Opts>(value.val, ctx, it, end);
             if (match<'"'>(ctx, it)) {
                return;
             }
          }
       };

       template <class T>
       struct to<JSON, quoted_helper<T>>
       {
          template <auto Opts>
          static void op(auto&& value, is_context auto&& ctx, auto&& b, auto&& ix) noexcept
          {
             dump<'"'>(b, ix);
             serialize<JSON>::op<Opts>(value.val, ctx, b, ix);
             dump<'"'>(b, ix);
          }
       };
    }

    template <auto MemPtr>
    constexpr decltype(auto) qouted()
    {
       return [](auto&& val) { return quoted_helper<std::decay_t<decltype(val.*MemPtr)>>{val.*MemPtr}; };
    }

    struct A
    {
       double x;
    };

    template <>
    struct glz::meta<A>
    {
      static constexpr auto value = object("x", qouted<&A::x>());
      // or...
      //static constexpr auto value = object("x", [](auto&& val) { return quoted_helper(val.x); });
    };

    void example() {
      A a{3.14};
      std::string buffer{};
      expect(not glz::write_json(a, buffer));
      expect(buffer == R"({"x":"3.14"})");

      buffer = R"({"x":"999.2"})";
      expect(not glz::read_json(a, buffer));
      expect(a.x == 999.2);
    }

## Custom Type Parsing

Custom wrappers allow you to define specialized serialization/deserialization behavior for specific types. This is useful when working with formats that differ from standard JSON representations.

### Example: Python-style Booleans

Some systems (like Python) represent booleans as `True`/`False` instead of JSON's standard `true`/`false`. Here's how to create a custom wrapper to handle this format:

#### Step 1: Define the Wrapper Helper

    template <class T>
    struct python_bool_helper
    {
       T& val;  // Reference to the actual boolean value
    };

#### Step 2: Specialize the Parser (`from`)

    namespace glz
    {
       template <class T>
       struct from<JSON, python_bool_helper<T>>
       {
          template <auto Opts>
          static void op(auto&& value, auto&& ctx, auto&& it, auto&& end)
          {
             skip_ws<Opts>(ctx, it, end);

             if (it >= end) {
                ctx.error = error_code::unexpected_end;
                return;
             }

             // Check for 'T' (True) or 'F' (False)
             if (*it == 'T') {
                if (it + 4 <= end && std::string_view(it, 4) == "True") {
                   value.val = true;
                   it += 4;
                } else {
                   ctx.error = error_code::expected_true_or_false;
                }
             } else if (*it == 'F') {
                if (it + 5 <= end && std::string_view(it, 5) == "False") {
                   value.val = false;
                   it += 5;
                } else {
                   ctx.error = error_code::expected_true_or_false;
                }
             } else {
                ctx.error = error_code::expected_true_or_false;
             }
          }
       };

#### Step 3: Specialize the Serializer (`to`)

       template <class T>
       struct to<JSON, python_bool_helper<T>>
       {
          template <auto Opts, class B>
          static void op(auto&& value, is_context auto&&, B&& b, auto&& ix) noexcept
          {
             if (value.val) {
                std::memcpy(&b[ix], "True", 4);
                ix += 4;
             } else {
                std::memcpy(&b[ix], "False", 5);
                ix += 5;
             }
          }
       };
    }

#### Step 4: Create a Convenience Function

    template <auto MemPtr>
    constexpr decltype(auto) python_bool()
    {
       return [](auto&& val) {
          return python_bool_helper<std::decay_t<decltype(val.*MemPtr)>>{val.*MemPtr};
       };
    }

#### Step 5: Use in Your Structs

    struct ConfigData
    {
       std::string name;
       int port;
       bool debug_mode;
       bool enable_logging;
    };

    template <>
    struct glz::meta<ConfigData>
    {
       using T = ConfigData;
       static constexpr auto value = object(
          &T::name,
          &T::port,
          "debug_mode", python_bool<&T::debug_mode>(),      // Custom wrapper for this field
          "enable_logging", python_bool<&T::enable_logging>() // Custom wrapper for this field
       );
    };

#### Usage Example

    ConfigData config{.name = "MyApp", .port = 8080, .debug_mode = true, .enable_logging = false};

    // Writing produces: {"name":"MyApp","port":8080,"debug_mode":True,"enable_logging":False}
    std::string json_output;
    glz::write_json(config, json_output);

    // Reading accepts Python-style booleans
    std::string python_json = R"({"name":"TestApp","port":3000,"debug_mode":False,"enable_logging":True})";
    ConfigData parsed_config;
    glz::read_json(parsed_config, python_json);

### Alternative Approaches

1. Custom Type Wrapper: Create your own boolean type with `glz::to`/`glz::from` specializations, allowing pure reflection usage
2. Global Override: Override boolean parsing globally (affects all boolean fields)

The custom wrapper approach shown above provides the most flexibility and performance, allowing field-by-field control over serialization behavior.