- Observer Schema Context —
ObjectTree._observerhook signature extended with 5th parameter:fn(op, path, key, val, schema). Theschemaparameter provides the schema node for the accessed property, enabling:- Filter properties by
x-observableor other extensions - Format-aware serialization based on
schema.format - Type-aware logging with
schema.typeandschema.description - Reactive bindings with full schema metadata in event payload
- Filter properties by
- Backward compatible — existing observer hooks with 4-parameter signature continue to work (5th parameter ignored if unused)
- Observer examples —
examples/observer_schema_context.mjsdemonstrates 4 use cases: filter by extension, format-aware serialization, type-aware logging, schema-less property handling
- All existing tests pass: 922/922 Draft-07 + 19/19 validate tests
- Dot Key Support — nested property paths via dot notation syntax.
obj["a.b.c"] = valueautomatically creates intermediate objects and validates against nested schema. Simplifies deep property updates without manual object traversal.- Auto-creates intermediate objects when undefined/null
- Full schema validation on final property
- Works with existing observer hooks
ObjectTree._observer— static hook point for property access observation. Set tofn(op, path, key, val)to observe all get/set across every ObjectTree instance.nulldisables (zero cost). Structural equivalent of Objective-C KVO — every schema-driven object is natively observable without wrapper or Proxy layer.getemits after value resolution (primitive, object wrap, array cursor)setemits after validation and write- Cached object/array re-access does not re-emit (cache hit returns before hook)
- 5 new dot key support tests in
test_dotkey.mjs: basic nested set, multiple paths, overwrite, type validation, auto-create intermediate - 6 observer tests in
validate.mjs: get primitive, get object, get array, set, null-disable, cached-no-re-emit - Total: 922/922 Draft-07 (100%)
- Lazy defaults — schema
defaultvalues are the initial value of any field. Reading a field missing from raw data returns its schema default transparently.Object.keys(tree)andininclude default-only fields. Applies at all nesting levels. - Array cursor — array properties wrap as a lazy Proxy cursor instead of eager
.map(). Per-index ObjectTree instances are created on first access and cached for stable identity (arr[0] === arr[0]). Mutations invalidate only the affected index.for...of, spread, and destructuring work correctly. $withDefaults()semantics — materializes defaults into raw data. Recursively fills nested objects and array items.$valueincludes defaults — includes schema-default fields and expands array item defaults.
- ctx pipeline — internal rewrite: C-style ctx struct + pure fn pipeline replaces class methods. External API unchanged.
- Removed graph scheduler — simplified execution model.
- Nested remote
$ref— correctly resolves nested$refin remote schemas.
- Python and Rust implementations have not been updated for 0.5.0 changes (lazy defaults, array cursor, ctx pipeline). They remain at 0.4.x semantics.
- Filesystem resolver no longer accepts HTTP URIs —
$refwithhttp://scheme now throwsTypeErrorwhen resolver is a directory path string. Use function or object-map resolver for HTTP schemas. - Resolver no longer retries with original
$ref— only the normalized URI is passed to resolver. Resolver must handle the canonical form.
- Loader
#idMaplookup (JS) — removed incorrect fallback that searched fragment-bearing URIs against$idmap - Test runner — switched from filesystem resolver to function resolver for Draft-07 remote
$reftests, correctly exercising the resolver callback interface
#fileExistshelper,.jsonextension auto-append, path traversal guard — these were runtime concerns that don't belong in schema tooling
- Unified
#valuestorage — removed#isObjectNode()and dual#value/#datafields. Every schema node is an object;type/propertiesare just attributes, not node classification. Schemas withouttypeorproperties(e.g. pureanyOf/oneOf) now correctly build ObjectTree. - Removed file path as schema parameter — schema must be a plain object. Read and parse files yourself; the lib doesn't do I/O.
- Nested wrapping for schemeless nodes (JS) — schemas without
type: "object"orpropertiesnow correctly wrap nested data as ObjectTree - Primitive data storage (JS) —
ObjectTree("hello", { type: "string" })now correctly stores and returns the value
#definePropertiesguard (JS) — handlesnull/boolean schemas without crashing- README — rewritten constructor docs, added "Loading Schema from File" section
$prefix on all ObjectTree API —value→$value,toDict()→$toDict(),oneOf()→$oneOf(), etc. Data properties keep no prefix (tree.name). Prevents collision when schema property names match API method names.
toJSON()protocol (JS) —JSON.stringify(tree)now works correctly, recursively unwraps nested ObjectTree instances- Invalid schema guard (JS) —
validateTyperejectsnull/non-object schemas with clear error message
- Eager nested wrapping (JS) — nested objects eagerly wrapped as ObjectTree at construction, aligned with Python/Rust. Mutation at any depth validates against sub-schema.
- Default preservation (JS) — constructor overlays data onto defaults instead of replacing;
set $valuealso preserves defaults - anyOf/oneOf sub-schema wrapping (JS) — removed
isObjguard; any object value is wrapped regardless of schema shape $project()(JS) — uses constructor instead of direct#dataassignment, ensuring nested wrapping
configurable: false(JS) — property descriptors blockdeleteto prevent validation bypass
- Rust codegen (
rust/codegen/) — generates static.rsfile: runtime source + typed structs from JSON Schemapython rust/codegen/codegen.py schema.json --out src/generated.rs- Full draft-07 suite (922/922) + API tests (44)
docs/— architecture docs:schema-as-object.md,schema2object-api.json,structure.md,types.md, etc.- JS
validate()— standalone validation export without constructing ObjectTree - Rust
schema()/get_schema()— now useschema_to_dict, aligned with JS/Python
- Internal re-bind Loader propagation (JS + Python + Rust) —
oneOf/anyOf/allOf/ifThen/project/withDefaultsnow pass Loader instance internally; sub-schemas no longer lose$definitionscontext - Rust
get_extensions()— empty path now callsself.schema()viaschema_to_dict, consistent with JS/Python multipleOfprecision — relative tolerance aligned across all three implementations- Resolver propagation —
ifThen/project/withDefaultscorrectly propagate resolver (JS + Python) requiredordering — cross-language alignment
- Rust rewritten as single file (
schema2object.rs), aligned with JS/Python structure - Python rewritten as single file (
schema2object.py), full draft-07 suite - JS full Draft-07
$refresolution with flexible resolver API
- Three-language implementation: JS, Python, Rust
- JSON Schema Draft-07 as object class definition
ObjectTreeconstruction validates; mutation validates;to_dict()projects schema fields only- Draft-07 combinators as methods:
oneOf,anyOf,allOf,notOf,ifThen,project,contains,withDefaults