From 55446448c0cd836241e8731d6af5091f0a82a14f Mon Sep 17 00:00:00 2001 From: "Claudius \"keldu\" Holeksa" Date: Sat, 7 Nov 2026 14:57:56 +0100 Subject: Reintroduced non strict mode --- modules/codec-json/c++/json.hpp | 17 ++++++++- modules/codec-json/c++/json.tmpl.hpp | 61 ++++++++++++++++++--------------- modules/codec-json/tests/codec-json.cpp | 50 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 28 deletions(-) diff --git a/modules/codec-json/c++/json.hpp b/modules/codec-json/c++/json.hpp index 1da74b1..a982bf3 100644 --- a/modules/codec-json/c++/json.hpp +++ b/modules/codec-json/c++/json.hpp @@ -66,6 +66,12 @@ struct codec_config { uint64_t depth = 16u; uint64_t length = 4096u; bool pretty = false; + /** + * Strict decoding demands a fully described dataset. + * Every member of a Struct has to be present in the json data. + * Non strict decoding is the default, since configuration files + * usually only describe the deviations from the default values. + */ bool strict = false; bool is_within(uint64_t bytes, uint64_t d){ @@ -117,7 +123,7 @@ public: buffer_view buff_v{from_decode.get_buffer()}; impl::json_helper::skip_whitespace(buff_v); - auto eov = impl::json_decode::decode(buff_v, to_decode); + auto eov = impl::json_decode::decode(buff_v, to_decode, cfg_); if(eov.is_error()){ return std::move(eov.get_error()); } @@ -129,6 +135,15 @@ public: void set_pretty(bool pretty){ cfg_.pretty = pretty; } + + /** + * Strict decoding demands a fully described dataset. + * Non strict decoding allows missing struct members, which keep + * their default value in that case. + */ + void set_strict(bool strict){ + cfg_.strict = strict; + } }; } diff --git a/modules/codec-json/c++/json.tmpl.hpp b/modules/codec-json/c++/json.tmpl.hpp index 4c6b2f0..a2faf08 100644 --- a/modules/codec-json/c++/json.tmpl.hpp +++ b/modules/codec-json/c++/json.tmpl.hpp @@ -489,7 +489,7 @@ template struct json_decode, ToDecode> { using Schema = schema::Primitive; - static error_or decode(buffer_view& buff, data& to) { + static error_or decode(buffer_view& buff, data& to, const codec_config&) { assert( ( buff.read() >= '0' && buff.read() <= '9') || ( buff.read() == '+' || buff.read() == '-') @@ -580,7 +580,7 @@ template struct json_decode { using Schema = schema::Bool; - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config&){ SAW_ASSERT(buff.read() == 't' || buff.read() == 'f'){ return make_error("Invalid JSON boolean"); } @@ -609,7 +609,7 @@ template struct json_decode { using Schema = schema::String; - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config&){ assert(buff.read() == '"'); buff.read_advance(1); @@ -697,7 +697,7 @@ struct json_decode...>, ToDecode> { using Schema = schema::Struct...>; template - static error_or decode_field_search(buffer_view& buff, data& to, std::array& fields, const data& search_name){ + static error_or decode_field_search(buffer_view& buff, data& to, std::array& fields, const data& search_name, const codec_config& cfg){ if constexpr ( i < sizeof...(T)){ using Type = typename parameter_pack_type::type; constexpr static string_literal Literal = parameter_key_pack_type::literal; @@ -706,12 +706,15 @@ struct json_decode...>, ToDecode> { return make_error("Duplicate entry in Struct"); } fields[i] = true; - auto eov = json_decode::decode(buff, to.template get()); + auto eov = json_decode::decode(buff, to.template get(), cfg); if(eov.is_error()){ return eov; } }else { - decode_field_search(buff, to, fields, search_name); + auto eov = decode_field_search(buff, to, fields, search_name, cfg); + if(eov.is_error()){ + return eov; + } } }else { return make_error("Field in Struct not found"); @@ -719,13 +722,13 @@ struct json_decode...>, ToDecode> { return void_t{}; } - static error_or decode_fields(buffer_view& buff, data& to, std::array& fields){ + static error_or decode_fields(buffer_view& buff, data& to, std::array& fields, const codec_config& cfg){ for(;;){ /** * Decode the member name/key */ data name; - auto eov = json_decode::decode(buff, name); + auto eov = json_decode::decode(buff, name, cfg); if(eov.is_error()){ return eov; } @@ -748,7 +751,7 @@ struct json_decode...>, ToDecode> { * Decode the field value */ { - auto eov = decode_field_search<0>(buff, to, fields, name); + auto eov = decode_field_search<0>(buff, to, fields, name, cfg); if(eov.is_error()){ return eov; } @@ -763,10 +766,14 @@ struct json_decode...>, ToDecode> { if(buff.read() == ','){ buff.read_advance(1); }else if(buff.read() == '}'){ - // If not all fields are set, the dataset is incomplete - for(auto& iter : fields){ - if(!iter){ - return make_error("Not all fields were set"); + // If not all fields are set, the dataset is incomplete. + // Non strict decoding leaves the missing members at their + // current/default value instead. + if(cfg.strict){ + for(auto& iter : fields){ + if(!iter){ + return make_error("Not all fields were set"); + } } } buff.read_advance(1); @@ -784,7 +791,7 @@ struct json_decode...>, ToDecode> { return void_t{}; } - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config& cfg){ std::array found_fields; std::fill(found_fields.begin(), found_fields.end(), false); @@ -799,14 +806,14 @@ struct json_decode...>, ToDecode> { // Check if there are no elements present in the JSON Struct if(buff.read() == '}'){ - if(sizeof...(T) > 0){ + if(sizeof...(T) > 0 && cfg.strict){ return make_error("Expected non empty struct"); } buff.read_advance(1); return void_t{}; } - auto eov = decode_fields(buff, to, found_fields); + auto eov = decode_fields(buff, to, found_fields, cfg); if(eov.is_error()){ return eov; } @@ -820,7 +827,7 @@ struct json_decode, ToDecode> { using Schema = schema::Tuple; template - static error_or decode_element(buffer_view& buff, data& to){ + static error_or decode_element(buffer_view& buff, data& to, const codec_config& cfg){ if constexpr (i < sizeof...(T)){ if constexpr ( i > 0 ){ if(buff.read() != ','){ @@ -834,7 +841,7 @@ struct json_decode, ToDecode> { } using Type = typename parameter_pack_type::type; - auto eov = json_decode::decode(buff, to.template get()); + auto eov = json_decode::decode(buff, to.template get(), cfg); if(eov.is_error()){ return eov; } @@ -843,7 +850,7 @@ struct json_decode, ToDecode> { return make_error(); } - eov = decode_element(buff, to); + eov = decode_element(buff, to, cfg); if(eov.is_error()){ return eov; } @@ -856,7 +863,7 @@ struct json_decode, ToDecode> { return void_t{}; } - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config& cfg){ assert(buff.read() == '['); buff.read_advance(1); @@ -865,7 +872,7 @@ struct json_decode, ToDecode> { return make_error(); } - auto eov = decode_element<0>(buff, to); + auto eov = decode_element<0>(buff, to, cfg); if(eov.is_error()){ return eov; } @@ -880,7 +887,7 @@ struct json_decode, ToDecode> { using Schema = schema::Array; template - static error_or decode_flat_level(buffer_view& buff, std::vector>& to, data, ToDecode>& index, data, ToDecode>& dims, bool log_dim){ + static error_or decode_flat_level(buffer_view& buff, std::vector>& to, data, ToDecode>& index, data, ToDecode>& dims, bool log_dim, const codec_config& cfg){ if constexpr (Level == D) { json_helper::skip_whitespace(buff); @@ -889,7 +896,7 @@ struct json_decode, ToDecode> { }catch(std::exception& e){ return make_error(); } - auto eov = json_decode::decode(buff, to.back()); + auto eov = json_decode::decode(buff, to.back(), cfg); if(eov.is_error()){ return eov; } @@ -913,7 +920,7 @@ struct json_decode, ToDecode> { index.at(Level) = 0; for(;!is_empty;){ // We should have an element right now - auto eov = decode_flat_level(buff,to,index,dims, index.at(Level).get() == 0 && log_dim); + auto eov = decode_flat_level(buff,to,index,dims, index.at(Level).get() == 0 && log_dim, cfg); if(eov.is_error()){ return eov; } @@ -964,11 +971,11 @@ struct json_decode, ToDecode> { return void_t{}; } - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config& cfg){ data, ToDecode> index; data, ToDecode> dims; std::vector> flat_array; - auto eov = decode_flat_level<0>(buff, flat_array, index, dims, true); + auto eov = decode_flat_level<0>(buff, flat_array, index, dims, true, cfg); if(eov.is_error()){ return eov; } @@ -984,7 +991,7 @@ template struct json_decode, ToDecode> { using Schema = schema::FixedArray; - static error_or decode(buffer_view& buff, data& to){ + static error_or decode(buffer_view& buff, data& to, const codec_config&){ return make_error("Json FixedArray decoding not implemented."); } diff --git a/modules/codec-json/tests/codec-json.cpp b/modules/codec-json/tests/codec-json.cpp index ab33f5a..a87b237 100644 --- a/modules/codec-json/tests/codec-json.cpp +++ b/modules/codec-json/tests/codec-json.cpp @@ -323,6 +323,56 @@ SAW_TEST("Struct read and write"){ SAW_EXPECT(native.get<"bar">() == "baz", "Invalid value for bar"); } +SAW_TEST("Struct read with missing member"){ + using namespace saw; + data native; + data json{"{\"bar\":\"baz\"}"}; + + codec json_codec; + + error_or eov = json_codec.decode(json, native); + SAW_EXPECT(eov.is_value(), "Decoding error"); + SAW_EXPECT(native.get<"foo">().get() == 0, "Missing member foo should keep its default value"); + SAW_EXPECT(native.get<"bar">() == "baz", "Invalid value for bar"); +} + +SAW_TEST("Struct read with missing member in strict mode"){ + using namespace saw; + data native; + data json{"{\"bar\":\"baz\"}"}; + + codec_config json_config; + json_config.strict = true; + codec json_codec{json_config}; + + error_or eov = json_codec.decode(json, native); + SAW_EXPECT(eov.is_error(), "Strict decoding should reject incomplete structs"); +} + +SAW_TEST("Empty Struct read"){ + using namespace saw; + data native; + data json{"{}"}; + + codec json_codec; + + error_or eov = json_codec.decode(json, native); + SAW_EXPECT(eov.is_value(), "Decoding error"); + SAW_EXPECT(native.get<"foo">().get() == 0, "Missing member foo should keep its default value"); + SAW_EXPECT(native.get<"bar">() == "", "Missing member bar should keep its default value"); +} + +SAW_TEST("Struct read with unknown member"){ + using namespace saw; + data native; + data json{"{\"foo\":5,\"baz\":3,\"bar\":\"baz\"}"}; + + codec json_codec; + + error_or eov = json_codec.decode(json, native); + SAW_EXPECT(eov.is_error(), "Unknown members are not supported"); +} + SAW_TEST("Array Struct Empty read and write"){ using namespace saw; data native{0u}; -- cgit v1.3.1