diff --git a/contrib/schemagen/example.schema.json b/contrib/schemagen/example.schema.json index e1c0f6b..a0ff867 100644 --- a/contrib/schemagen/example.schema.json +++ b/contrib/schemagen/example.schema.json @@ -52,6 +52,14 @@ } } }, + "loose": { + "type": "object", + "properties": { + "known": { + "type": "string" + } + } + }, "friends": { "type": "array", "items": { diff --git a/contrib/schemagen/test_gen.cpp b/contrib/schemagen/test_gen.cpp index 577e983..c52d9bc 100644 --- a/contrib/schemagen/test_gen.cpp +++ b/contrib/schemagen/test_gen.cpp @@ -186,6 +186,20 @@ int main() { expectReject(R"([1,2,3])", "array where object expected (root)"); expectReject(R"({"name":"x","age":1,)", "truncated / invalid json"); + // ---- duplicate keys in permissive (additionalProperties allowed) object + // ---- + { + RootBuilder b; + WeaselJsonStatus s = + parseStrided(b, R"({"name":"x","age":1,"loose":{"unknown":1}})"); + CHECK(s == WeaselJson_OK); + printf("ok single unknown key in non-strict object\n"); + } + expectReject(R"({"name":"x","age":1,"loose":{"unknown":1,"unknown":2}})", + "duplicate unknown key in non-strict object"); + expectReject(R"({"name":"x","age":1,"loose":{"known":"a","known":"b"}})", + "duplicate known key in non-strict object"); + if (failures == 0) { printf("\nALL TESTS PASSED\n"); return 0; diff --git a/contrib/schemagen/weaseljson_schemagen.py b/contrib/schemagen/weaseljson_schemagen.py index 2b69085..3ce7c6e 100644 --- a/contrib/schemagen/weaseljson_schemagen.py +++ b/contrib/schemagen/weaseljson_schemagen.py @@ -566,6 +566,7 @@ class Emitter: #include #include #include +#include #include #include "weaseljson.h" @@ -931,6 +932,7 @@ private: void *dest; int field = -1; // object: selected field (-1 want key, -2 skip) std::vector seen; // populated field bitset (object frames) + std::unordered_set unknown; // unknown keys seen in non-strict objects }}; static constexpr int kWantKey = -1; static constexpr int kSkip = -2; @@ -1014,12 +1016,16 @@ private: scratch_.append(buf, len); if (!done) return; int idx = matchKey(f.kind, scratch_); - scratch_.clear(); if (idx < 0) {{ if (isStrict(f.kind)) {{ reject(); return; }} + if (!f.unknown.insert(scratch_).second) {{ + reject(); return; // duplicate unknown key + }} + scratch_.clear(); f.field = kSkip; return; }} + scratch_.clear(); if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{ reject(); return; // duplicate key }}