diff options
| author | Claudius "keldu" Holeksa <mail@keldu.de> | 2026-11-07 14:57:56 +0100 |
|---|---|---|
| committer | Claudius "keldu" Holeksa <mail@keldu.de> | 2026-11-07 14:57:56 +0100 |
| commit | 55446448c0cd836241e8731d6af5091f0a82a14f (patch) | |
| tree | 635f8b503014f21558e42fe1cd7f851546e0cf9d | |
| parent | 97ae1a8f23074fea3784a008921c5d9fc3c1e8fb (diff) | |
| download | forstio-forstio-55446448c0cd836241e8731d6af5091f0a82a14f.tar.gz | |
Reintroduced non strict mode
| -rw-r--r-- | modules/codec-json/c++/json.hpp | 17 | ||||
| -rw-r--r-- | modules/codec-json/c++/json.tmpl.hpp | 61 | ||||
| -rw-r--r-- | 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<encode::Json> { 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<Schema, ToEncoding>::decode(buff_v, to_decode); + auto eov = impl::json_decode<Schema, ToEncoding>::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<typename T, size_t N, typename ToDecode> struct json_decode<schema::Primitive<T,N>, ToDecode> { using Schema = schema::Primitive<T,N>; - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to) { + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>&) { assert( ( buff.read() >= '0' && buff.read() <= '9') || ( buff.read() == '+' || buff.read() == '-') @@ -580,7 +580,7 @@ template<typename ToDecode> struct json_decode<schema::Bool, ToDecode> { using Schema = schema::Bool; - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>&){ SAW_ASSERT(buff.read() == 't' || buff.read() == 'f'){ return make_error<err::invalid_state>("Invalid JSON boolean"); } @@ -609,7 +609,7 @@ template<typename ToDecode> struct json_decode<schema::String, ToDecode> { using Schema = schema::String; - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>&){ assert(buff.read() == '"'); buff.read_advance(1); @@ -697,7 +697,7 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, ToDecode> { using Schema = schema::Struct<schema::Member<T,Lits>...>; template<std::size_t i> - static error_or<void> decode_field_search(buffer_view& buff, data<Schema, ToDecode>& to, std::array<bool, sizeof...(T)>& fields, const data<schema::String,ToDecode>& search_name){ + static error_or<void> decode_field_search(buffer_view& buff, data<Schema, ToDecode>& to, std::array<bool, sizeof...(T)>& fields, const data<schema::String,ToDecode>& search_name, const codec_config<encode::Json>& cfg){ if constexpr ( i < sizeof...(T)){ using Type = typename parameter_pack_type<i, T...>::type; constexpr static string_literal Literal = parameter_key_pack_type<i, Lits...>::literal; @@ -706,12 +706,15 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, ToDecode> { return make_error<err::invalid_state>("Duplicate entry in Struct"); } fields[i] = true; - auto eov = json_decode<Type, ToDecode>::decode(buff, to.template get<Literal>()); + auto eov = json_decode<Type, ToDecode>::decode(buff, to.template get<Literal>(), cfg); if(eov.is_error()){ return eov; } }else { - decode_field_search<i+1>(buff, to, fields, search_name); + auto eov = decode_field_search<i+1>(buff, to, fields, search_name, cfg); + if(eov.is_error()){ + return eov; + } } }else { return make_error<err::invalid_state>("Field in Struct not found"); @@ -719,13 +722,13 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, ToDecode> { return void_t{}; } - static error_or<void> decode_fields(buffer_view& buff, data<Schema, ToDecode>& to, std::array<bool, sizeof...(T)>& fields){ + static error_or<void> decode_fields(buffer_view& buff, data<Schema, ToDecode>& to, std::array<bool, sizeof...(T)>& fields, const codec_config<encode::Json>& cfg){ for(;;){ /** * Decode the member name/key */ data<schema::String, ToDecode> name; - auto eov = json_decode<schema::String, ToDecode>::decode(buff, name); + auto eov = json_decode<schema::String, ToDecode>::decode(buff, name, cfg); if(eov.is_error()){ return eov; } @@ -748,7 +751,7 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, 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<schema::Struct<schema::Member<T,Lits>...>, 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<err::invalid_state>("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<err::invalid_state>("Not all fields were set"); + } } } buff.read_advance(1); @@ -784,7 +791,7 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, ToDecode> { return void_t{}; } - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>& cfg){ std::array<bool, sizeof...(T)> found_fields; std::fill(found_fields.begin(), found_fields.end(), false); @@ -799,14 +806,14 @@ struct json_decode<schema::Struct<schema::Member<T,Lits>...>, 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<err::invalid_state>("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<schema::Tuple<T...>, ToDecode> { using Schema = schema::Tuple<T...>; template<std::size_t i> - static error_or<void> decode_element(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode_element(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>& cfg){ if constexpr (i < sizeof...(T)){ if constexpr ( i > 0 ){ if(buff.read() != ','){ @@ -834,7 +841,7 @@ struct json_decode<schema::Tuple<T...>, ToDecode> { } using Type = typename parameter_pack_type<i, T...>::type; - auto eov = json_decode<Type, ToDecode>::decode(buff, to.template get<i>()); + auto eov = json_decode<Type, ToDecode>::decode(buff, to.template get<i>(), cfg); if(eov.is_error()){ return eov; } @@ -843,7 +850,7 @@ struct json_decode<schema::Tuple<T...>, ToDecode> { return make_error<err::buffer_exhausted>(); } - eov = decode_element<i+1>(buff, to); + eov = decode_element<i+1>(buff, to, cfg); if(eov.is_error()){ return eov; } @@ -856,7 +863,7 @@ struct json_decode<schema::Tuple<T...>, ToDecode> { return void_t{}; } - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>& cfg){ assert(buff.read() == '['); buff.read_advance(1); @@ -865,7 +872,7 @@ struct json_decode<schema::Tuple<T...>, ToDecode> { return make_error<err::buffer_exhausted>(); } - 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<schema::Array<T,D>, ToDecode> { using Schema = schema::Array<T,D>; template<size_t Level> - static error_or<void> decode_flat_level(buffer_view& buff, std::vector<data<T, encode::Native>>& to, data<schema::FixedArray<schema::UInt64,D>, ToDecode>& index, data<schema::FixedArray<schema::UInt64,D>, ToDecode>& dims, bool log_dim){ + static error_or<void> decode_flat_level(buffer_view& buff, std::vector<data<T, encode::Native>>& to, data<schema::FixedArray<schema::UInt64,D>, ToDecode>& index, data<schema::FixedArray<schema::UInt64,D>, ToDecode>& dims, bool log_dim, const codec_config<encode::Json>& cfg){ if constexpr (Level == D) { json_helper::skip_whitespace(buff); @@ -889,7 +896,7 @@ struct json_decode<schema::Array<T,D>, ToDecode> { }catch(std::exception& e){ return make_error<err::out_of_memory>(); } - auto eov = json_decode<T, ToDecode>::decode(buff, to.back()); + auto eov = json_decode<T, ToDecode>::decode(buff, to.back(), cfg); if(eov.is_error()){ return eov; } @@ -913,7 +920,7 @@ struct json_decode<schema::Array<T,D>, ToDecode> { index.at(Level) = 0; for(;!is_empty;){ // We should have an element right now - auto eov = decode_flat_level<Level+1>(buff,to,index,dims, index.at(Level).get() == 0 && log_dim); + auto eov = decode_flat_level<Level+1>(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<schema::Array<T,D>, ToDecode> { return void_t{}; } - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>& cfg){ data<schema::FixedArray<schema::UInt64,D>, ToDecode> index; data<schema::FixedArray<schema::UInt64,D>, ToDecode> dims; std::vector<data<T,encode::Native>> 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<typename T, uint64_t... Dim, typename ToDecode> struct json_decode<schema::FixedArray<T,Dim...>, ToDecode> { using Schema = schema::FixedArray<T,Dim...>; - static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to){ + static error_or<void> decode(buffer_view& buff, data<Schema, ToDecode>& to, const codec_config<encode::Json>&){ return make_error<err::not_implemented>("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<schema::TestStruct, encode::Native> native; + data<schema::TestStruct, encode::Json> json{"{\"bar\":\"baz\"}"}; + + codec<schema::TestStruct, encode::Json> json_codec; + + error_or<void> 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<schema::TestStruct, encode::Native> native; + data<schema::TestStruct, encode::Json> json{"{\"bar\":\"baz\"}"}; + + codec_config<encode::Json> json_config; + json_config.strict = true; + codec<schema::TestStruct, encode::Json> json_codec{json_config}; + + error_or<void> 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<schema::TestStruct, encode::Native> native; + data<schema::TestStruct, encode::Json> json{"{}"}; + + codec<schema::TestStruct, encode::Json> json_codec; + + error_or<void> 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<schema::TestStruct, encode::Native> native; + data<schema::TestStruct, encode::Json> json{"{\"foo\":5,\"baz\":3,\"bar\":\"baz\"}"}; + + codec<schema::TestStruct, encode::Json> json_codec; + + error_or<void> 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<schema::TestArrayStruct, encode::Native> native{0u}; |
