Files
andrew c44b0262bc
CI / pre-commit (push) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (push) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (push) Successful in 50s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (push) Successful in 1m36s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (push) Successful in 1m34s
Merge pull request 'schemagen: reject absent additionalProperties' (#58) from weaselbot/weaseljson:weaselbot/issue-55 into main
Reviewed-on: #58
2026-07-20 18:06:53 +00:00
..

weaseljson schemagen

Generates a C++ type and a streaming builder parser from a JSON Schema, on top of weaseljson.

The builder copies incoming bytes straight into their final destinations in the result struct as they arrive (no intermediate DOM). When parsing completes you take() ownership of the result.

python3 weaseljson_schemagen.py schema.json -o parsed.h --namespace myschema

Usage

#include "parsed.h"
using namespace myschema;

RootBuilder b;
WeaselJsonStatus s = b.feed(buf, len);   // call repeatedly with chunks; buf may
                                         // be modified in place (unescaping)
if (s == WeaselJson_AGAIN) s = b.finish();
if (s == WeaselJson_OK) {
  Root value = b.take();                 // ownership of the parsed result
}

feed/finish return the usual WeaselJsonStatus; WeaselJson_OK means the document is both valid JSON and schema-valid, and WeaselJson_REJECT covers both malformed JSON and schema violations. The builder holds interior pointers into the result, so it is non-movable.

Schema -> C++ mapping

JSON Schema C++
object with properties struct
required property value member
non-required property std::optional<T>
["T", "null"] (nullable) std::optional<T> (accepts null)
string / integer / number / boolean std::string / int64_t / double / bool
enum of strings enum class : int
array with items std::vector<T>
$ref to $defs/definitions the referenced named struct
recursive $ref std::unique_ptr<T> (cycle broken)
additionalProperties: false unknown keys rejected
additionalProperties absent / true not supported (rejected at generation)

Schema violations (rejected at parse time)

  • missing required property (top-level or nested)
  • wrong JSON type for a property / array element
  • null for a non-nullable slot
  • value not in a string enum
  • a number not representable in the target type (e.g. 1.5 for an integer)
  • duplicate object keys
  • unknown key in any object

Not supported (rejected at generation time, no fallback)

oneOf / anyOf / allOf / not / if-then-else, patternProperties, additionalProperties absent or set to true, additionalProperties with a schema (typed map), prefixItems (tuples), const, dependentSchemas/dependentRequired, union type lists other than ["T", "null"], non-string enums, and remote ($ref to other documents).

Notes

  • Strings (the bulk payload) are appended directly into their destination std::string, even when split across feed calls. Numbers are accumulated in a small scratch buffer and converted on completion, since an int64_t/double cannot hold partial digits.
  • test_gen.cpp generates from example.schema.json and exercises the parser byte-by-byte (covering chunked strings/numbers), plus the rejection cases.

Testing

The schemagen tests are registered with CTest and run as part of the default ctest invocation from the build directory:

cmake -S . -B build
make -C build -j "$(nproc)"
cd build
ctest --output-on-failure

For local development you can also use the convenience script:

cd contrib/schemagen
./run_tests.sh

Both regenerate the example parser (gen.h) and a regression parser with 40 required properties (big.h), compile test_gen.cpp and test_big.cpp, and run them. test_big.cpp specifically covers issue #3: it checks that a 40-property object accepts all fields, rejects a missing field at index 32, and rejects duplicate keys around the 32-bit boundary.