glaze icon indicating copy to clipboard operation
glaze copied to clipboard

Can I use both custom reader and optional at the same time?

Open toge opened this issue 5 months ago • 3 comments

I want to use both custom reader and optional at the same time.

In the code below, the custom reader works fine but can't properly handle null values. Is there a way to make both of these features work correctly?

#include <string>
#include <iostream>
#include <optional>

#include "glaze/glaze.hpp"

struct TestVal {
  std::optional<char> val;
};

template<>
struct glz::meta<TestVal> {
  static constexpr auto read_val =[](TestVal& v, const std::string& input) {
    if (input.empty()) {
      v.val = std::nullopt;
      return;
    }
    v.val = input[0];
  };

  static constexpr auto value = glz::object("val", glz::custom<read_val, nullptr>);
};

int main() {
  // Expected: print 'A'
  {
    std::string src = R"({"val":"ABC"})";
    TestVal v;

    auto ec = glz::read_json(v, src);
    if (ec) {
      std::cout << "Read failed\n";
    } else {
      std::cout << "Read successful: " << v.val.value_or('?') << '\n';
    }
  }

  // Expected: print '?'
  {
    std::string src = R"({"val":null})";
    TestVal v;

    auto ec = glz::read_json(v, src);
    if (ec) {
      std::cout << "Read failed " <<  glz::format_error(ec, src) << '\n';
    } else {
      std::cout << "Read successful: " << v.val.value_or('?') << '\n';
    }
  }
  return 0;
}

Result:

Read successful: A
Read failed 1:8: expected_quote
   {"val":null}
          ^

toge avatar Aug 03 '25 11:08 toge

I think I've looked into this before and it isn't trivial to implement efficiently. But, I would like to look into this again, so I'll keep this issue open until we can resolve it.

stephenberry avatar Aug 04 '25 13:08 stephenberry

@stephenberry Thank you for your consideration.

Since I understand the complexity of implementing this, I'll proceed with another approach for now. Even as it so, this library is already extremely useful.

toge avatar Aug 05 '25 04:08 toge

Look at this

GTruf avatar Aug 24 '25 21:08 GTruf

If you make your custom functions take a glz::generic then you can use all kinds of complex logic to handle reading/writing your optional value. This allows you to have custom handling for null values.

See examples in this pull request: #2080

Here's an example, where the member functions could also be external lambda functions in the glz::meta

struct generic_optional_int {
   std::optional<int> num;

   void read_val(const glz::generic& value) {
      if (value.is_null()) {
         num = std::nullopt;
         return;
      }

      if (value.is_number()) {
         num = value.as<int>();
         return;
      }

      if (value.is_string()) {
         const auto& str = value.get<std::string>();
         if (str.empty()) {
            num = std::nullopt;
            return;
         }

         int val{};
         auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), val);
         if (ec == std::errc{} && ptr == str.data() + str.size()) {
            num = val;
            return;
         }
      }

      num = std::nullopt;
   }

   glz::generic write_val() const {
      if (num.has_value()) {
         return num.value();
      }
      return nullptr;
   }
};

template <>
struct glz::meta<generic_optional_int> {
   using T = generic_optional_int;
   static constexpr auto value = glz::object(
      "num", glz::custom<&T::read_val, &T::write_val>
   );
};

I'm closing this issue because I think this should solve the original request.

stephenberry avatar Nov 22 '25 20:11 stephenberry