forked from weaselab/weaseljson
schemagen: reject duplicate unknown keys in non-strict objects
Track unknown keys in a per-object unordered_set so that permissive objects (additionalProperties absent/true) still reject duplicate keys, matching the README guarantee. - Add std::unordered_set<std::string> to Frame. - Insert unknown keys in cbKeyData and reject duplicates before skipping. - Add a permissive "loose" subobject to example.schema.json. - Test single unknown key accepted and duplicate unknown/known keys rejected.
This commit is contained in:
@@ -52,6 +52,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"loose": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"known": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"friends": {
|
"friends": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|||||||
@@ -186,6 +186,20 @@ int main() {
|
|||||||
expectReject(R"([1,2,3])", "array where object expected (root)");
|
expectReject(R"([1,2,3])", "array where object expected (root)");
|
||||||
expectReject(R"({"name":"x","age":1,)", "truncated / invalid json");
|
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) {
|
if (failures == 0) {
|
||||||
printf("\nALL TESTS PASSED\n");
|
printf("\nALL TESTS PASSED\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -566,6 +566,7 @@ class Emitter:
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "weaseljson.h"
|
#include "weaseljson.h"
|
||||||
@@ -931,6 +932,7 @@ private:
|
|||||||
void *dest;
|
void *dest;
|
||||||
int field = -1; // object: selected field (-1 want key, -2 skip)
|
int field = -1; // object: selected field (-1 want key, -2 skip)
|
||||||
std::vector<uint64_t> seen; // populated field bitset (object frames)
|
std::vector<uint64_t> seen; // populated field bitset (object frames)
|
||||||
|
std::unordered_set<std::string> unknown; // unknown keys seen in non-strict objects
|
||||||
}};
|
}};
|
||||||
static constexpr int kWantKey = -1;
|
static constexpr int kWantKey = -1;
|
||||||
static constexpr int kSkip = -2;
|
static constexpr int kSkip = -2;
|
||||||
@@ -1014,12 +1016,16 @@ private:
|
|||||||
scratch_.append(buf, len);
|
scratch_.append(buf, len);
|
||||||
if (!done) return;
|
if (!done) return;
|
||||||
int idx = matchKey(f.kind, scratch_);
|
int idx = matchKey(f.kind, scratch_);
|
||||||
scratch_.clear();
|
|
||||||
if (idx < 0) {{
|
if (idx < 0) {{
|
||||||
if (isStrict(f.kind)) {{ reject(); return; }}
|
if (isStrict(f.kind)) {{ reject(); return; }}
|
||||||
|
if (!f.unknown.insert(scratch_).second) {{
|
||||||
|
reject(); return; // duplicate unknown key
|
||||||
|
}}
|
||||||
|
scratch_.clear();
|
||||||
f.field = kSkip;
|
f.field = kSkip;
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
scratch_.clear();
|
||||||
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
|
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
|
||||||
reject(); return; // duplicate key
|
reject(); return; // duplicate key
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user