Skip to content

Reading JSON into a struct with a DefaultVal field and a size validator field always raises a size validation error #700

Description

@wbernoudy

For example, using rfl::json::read with the struct

struct Person {
  rfl::DefaultVal<std::string> name = "Homer Simpson";
  rfl::Validator<std::vector<std::string>, rfl::Size<rfl::Minimum<1>>> favorite_foods;
};

seems to always fail with the exception Size validation failed: Value expected to be greater than or equal to 1, but got 0., even when given a list of favorite_foods with one or more items.

A short test case to see the behavior:

namespace test_default_val_alongside_size_validator {

struct Person {
  rfl::DefaultVal<std::string> name = "Homer Simpson";
  rfl::Validator<std::vector<std::string>, rfl::Size<rfl::Minimum<1>>> favorite_foods;
};

TEST(json, test_default_val_alongside_size_validator) {
  static_assert(rfl::internal::has_default_val_v<Person>,
                "Should have default val");

  const auto should_fail = rfl::json::read<Person>(R"({})");

  EXPECT_EQ(should_fail && true, false)
      << "Should have failed due to missing required field";

  const auto homer =
      rfl::json::read<Person>(R"({"favorite_foods": ["donuts", "pizza"]})").value();

  EXPECT_EQ(homer.name.value(), "Homer Simpson");
  EXPECT_EQ(homer.favorite_foods.value().size(), 2);
  EXPECT_EQ(homer.favorite_foods.value()[0], "donuts");
  EXPECT_EQ(homer.favorite_foods.value()[1], "pizza");
}
}  // namespace test_default_val_alongside_size_validator

However, if you set a default value for the field that satisfies the validator, everything seems to work. For example, you can change the favorite_foods definition in the above test to

  rfl::Validator<std::vector<std::string>, rfl::Size<rfl::Minimum<1>>> favorite_foods = {{std::string{"beer"}}};

and it should pass.

Activity

  1. liuzicheng1987 commented on Jul 22, 2026

    @liuzicheng1987
    Collaborator

    @wbernoudy, yes, I am aware of this issue and at the moment, i can't really think of a good way to fix it.

    Here is what is happening: For normal parsing, we use this little trick:

        alignas(T) unsigned char buf[sizeof(T)]{};
        auto ptr = internal::ptr_cast<T*>(&buf);
        auto view = ProcessorsType::template process<T>(to_view(*ptr));
        using ViewType = std::remove_cvref_t<decltype(view)>;
        const auto [set, err] =
            Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);

    This allows us to read into the struct without actually initializing it. But when the struct has default values, we need to be able to use them, so we have to do this instead:

          auto t = T{};  // This might fail, for instance if the default value does
                         // not satisfy the validator, but in that case we will just
                         // return the error.
          auto view = ProcessorsType::template process<T>(to_view(t));
          using ViewType = decltype(view);
          const auto err =
              Parser<R, W, ViewType, ProcessorsType>::read_view_with_default(
                  _r, _var, &view);

    And as the comment indicates, we ran into this issue before. Honestly, I can't really think of a good way to get around this.

    But if you have an idea, I am always open to suggestions.

  2. wbernoudy commented on Jul 25, 2026

    @wbernoudy
    Author

    Thank you @liuzicheng1987, this context was very helpful for understanding what is going on.

    I thought about this for a while and the best I can come up with is using std::source_location to change the behavior of the default constructor of rfl::Validator to skip validation when it is being called from the parser specifically. Obviously this is quite "hacky", but I thought that since the library already relies on std::source_location, it might be acceptable.

    I will open a PR soon with an implementation so you can see what I mean.

  3. autoantwort commented on Aug 24, 2026

    @autoantwort
    Contributor

    Hm one could disable validation for default constructed values here:

    Validator() : value_(ValidationType::validate(T()).value()) {}

    I dont know what happens when we parse a struct with a missing field that has a validator: Is the default constructor called then? And could be detect that it is a Validator and pass a rfl::default_construct_tag to the constructor?

  4. cmami commented on Aug 26, 2026

    @cmami

    Note that the issue is even more serious than a mere "raises an exception" as Result.hpp throws std::runtime_exceptions in several functions while the call stack may have noexcept functions on top (at least Parser_default.hpp does). So expect abort()s on fairly innocent use cases.

    The main use case where I've struggled with this issue is a simple one: deserialize JSON that is not exactly compatible with the C++ structure instances to be populated. It seems to suffice to simply have JSON data that lacks a key-value pair mapped into a C++ structure. I've tried a remedy of using something like rfl::DefaultVal<My_Type> myMember{"MY_STRING"}; but that a) doesn't prevent the abort()s, and b) is against the use case idea of "this value has no sensible default - if it's missing from the deserialize data then throw user code catchable error". Note that a declaration like My_Type myMember; where the underlying type is std::string suffers from similar issues.

    Oh and the rfl::Pattern also plays a part in this scheme, but I fear it's not the only class suffering from the same disease. Something like:

    using My_Type = rfl::Pattern<R"((?:FirstVal|SecondVal)$)", "My_Type">;

    So, the [noexcept ... throw] is definitely a defect that should be addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions