diff --git a/doc/basics.md b/doc/basics.md index bcb52e772..118c38b22 100644 --- a/doc/basics.md +++ b/doc/basics.md @@ -1400,6 +1400,9 @@ maps it to parsing code. We call the default constructor, and then assign values to the public members. +If a key is missing in the JSON document, an error is generated (`NO_SUCH_FIELD`), +except if the attribute is of a type like `std::optional` (`simdjson::concepts::optional_type`). + #### Special cases However, there are instances where the construction cannot diff --git a/include/simdjson/concepts.h b/include/simdjson/concepts.h index 016540c96..dc44b3b9e 100644 --- a/include/simdjson/concepts.h +++ b/include/simdjson/concepts.h @@ -122,6 +122,7 @@ concept optional_type = requires(std::remove_cvref_t obj) { } -> std::convertible_to::value_type>; }; { static_cast(obj) } -> std::same_as; // convertible to bool + { obj.reset() } noexcept -> std::same_as; }; diff --git a/include/simdjson/generic/ondemand/std_deserialize.h b/include/simdjson/generic/ondemand/std_deserialize.h index ce85ed202..2827be06d 100644 --- a/include/simdjson/generic/ondemand/std_deserialize.h +++ b/include/simdjson/generic/ondemand/std_deserialize.h @@ -276,18 +276,26 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { } else { SIMDJSON_TRY(val.get_object().get(obj)); } - error_code e = simdjson::SUCCESS; template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) { constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem)); - // Note: removed static assert as optional types are now handled generically - // as long we are successful or the field is not found, we continue - if(e == simdjson::SUCCESS || e == simdjson::NO_SUCH_FIELD) { - e = obj[key].get(out.[:mem:]); + if constexpr (concepts::optional_type) { + // for optional members, it's ok if the key is missing + auto error = obj[key].get(out.[:mem:]); + if (error && error != NO_SUCH_FIELD) { + if(error == NO_SUCH_FIELD) { + out.[:mem:].reset(); + continue; + } + return error; + } + } else { + // for non-optional members, the key must be present + SIMDJSON_TRY(obj[key].get(out.[:mem:])); } } }; - return e; + return simdjson::SUCCESS; } // Support for enum deserialization - deserialize from string representation using expand approach from P2996R12 diff --git a/tests/ondemand/ondemand_convert_tests.cpp b/tests/ondemand/ondemand_convert_tests.cpp index bc9c8328d..4c8fadd61 100644 --- a/tests/ondemand/ondemand_convert_tests.cpp +++ b/tests/ondemand/ondemand_convert_tests.cpp @@ -151,13 +151,42 @@ simdjson::padded_string json_cars = } TEST_SUCCEED(); } - + struct Player { + std::string username; + int level; + double health; + }; struct BadPlayer { int username; // Oops, should be string! int level; double health; }; + struct OptionalPlayer { + std::string username; + std::optional level; + double health; + }; #if SIMDJSON_STATIC_REFLECTION + bool missing_key_player() { + TEST_START(); + std::string json = R"({"username":"Alice","health":100.0})"; + simdjson::padded_string padded(json); + Player p; + simdjson::ondemand::parser parser; + auto doc = parser.iterate(padded); + ASSERT_ERROR(doc.get(p), simdjson::NO_SUCH_FIELD); + TEST_SUCCEED(); + } + bool missing_key_optional_player() { + TEST_START(); + std::string json = R"({"username":"Alice","health":100.0})"; + simdjson::padded_string padded(json); + OptionalPlayer p; + simdjson::ondemand::parser parser; + auto doc = parser.iterate(padded); + ASSERT_SUCCESS(doc.get(p)); + TEST_SUCCEED(); + } bool bad_player() { TEST_START(); // username is a string but we declared it as an int @@ -478,7 +507,7 @@ bool test_to_vs_from_equivalence() { bool run() { return #if SIMDJSON_STATIC_REFLECTION - meeting_time_test() && meeting_test() && bad_player() && good_player() && complicated_weather_test() && + missing_key_optional_player() && missing_key_player() && meeting_time_test() && meeting_test() && bad_player() && good_player() && complicated_weather_test() && #endif #if SIMDJSON_SUPPORTS_CONCEPTS simple_no_except() &&