JSON Schema Draft-07 as a Rust object. Structure maps to accessors, logic maps to methods.
# Cargo.toml
schema-value = { path = "schema-value" } # local workspaceA JS object has state and behavior together. JSON serialization stripped the behavior.
schema-value restores it: the schema defines the class, the data is the instance.
use schema_value::SchemaValue;
use serde_json::json;
let schema = json!({
"type": "object",
"properties": {
"email": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"roles": {"type": "array", "items": {"enum": ["admin", "user", "guest"]}}
},
"required": ["email", "roles"]
});
let data = json!({"email": "alice@example.com", "age": 30, "roles": ["admin"]});
let user = SchemaValue::new(data, schema);
// Index access (structure → data)
assert_eq!(user["email"], json!("alice@example.com"));
assert_eq!(user["age"], json!(30));
// get() propagates sub-schema
let email_sv = user.get("email").unwrap(); // SchemaValue with email sub-schemaEach Draft-07 logical operator maps to a method on SchemaValue:
| Draft-07 keyword | Method | Semantics |
|---|---|---|
oneOf |
one_of() |
XOR — exactly one branch matches |
anyOf |
any_of() |
OR — all matching branches |
allOf |
all_of() |
AND — merge all sub-schemas |
not |
not_of(schema) |
NOT — data must not match |
if/then/else |
if_then() |
CASE WHEN — conditional branch |
properties |
project() |
SELECT — keep only schema-defined fields |
contains |
contains(schema) |
EXISTS — array element check |
let schema = json!({
"oneOf": [
{"properties": {"type": {"const": "text"}, "content": {"type": "string"}}},
{"properties": {"type": {"const": "image"}, "url": {"type": "string"}}},
]
});
let event = SchemaValue::new(
json!({"type": "text", "content": "hello"}),
schema
);
let branch = event.one_of()?; // SchemaValue bound to matching sub-schema
assert_eq!(branch["content"], json!("hello"));let schema = json!({
"if": {"properties": {"role": {"const": "admin"}}},
"then": {"properties": {"level": {"minimum": 5}}},
"else": {"properties": {"level": {"maximum": 4}}}
});
let user = SchemaValue::new(json!({"role": "admin", "level": 10}), schema);
let branch = user.if_then(); // bound to "then" branchlet schema = json!({
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
}
});
let raw = SchemaValue::new(
json!({"name": "Alice", "age": 30, "extra": "noise"}),
schema
);
let clean = raw.project()?; // {"name": "Alice", "age": 30}When your schema defines x-methods, extract the method body and validate against it:
let schema = json!({
"properties": {"age": {"type": "integer", "minimum": 0}},
"x-methods": {
"is_adult": {
"properties": {"age": {"minimum": 18}}
}
}
});
let user = SchemaValue::new(json!({"age": 20}), schema.clone());
// Extract x-methods body, validate as sub-schema
let method_schema = schema["x-methods"]["is_adult"].clone();
let check = SchemaValue::new(user.as_value().clone(), method_schema);
check.validate()?; // Ok — age >= 18use schema_value::SchemaValue;
use serde_json::json;
let schema = json!({
"properties": {
"email": {"type": "string"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["email"]
});
// Valid
let user = SchemaValue::new(json!({"email": "alice@example.com", "age": 30}), schema.clone());
assert!(user.validate().is_ok());
// Invalid — returns Err with path + reason
let bad = SchemaValue::new(json!({"age": -1}), schema);
let errs = bad.validate().unwrap_err();
// errs[0]: path="", message="missing required field 'email'"// sv["key"] → &Value (fast, no schema propagation)
let raw: &Value = &sv["name"];
// sv.get("key") → Option<SchemaValue> (propagates sub-schema)
let name_sv: Option<SchemaValue> = sv.get("name");Use get() when you need the sub-schema context (e.g., before calling composition methods on a nested object). Use [] for simple data access.
SchemaValue treats the schema as a class definition:
let schema = json!({
"properties": {
"name": {"type": "string", "minLength": 1},
"score": {"type": "number", "minimum": 0, "maximum": 100}
},
"required": ["name"],
"x-methods": {
"passed": {"properties": {"score": {"minimum": 60}}}
}
});
// Instance
let student = SchemaValue::new(json!({"name": "Alice", "score": 85}), schema.clone());
// Access state
assert_eq!(student["name"], json!("Alice"));
assert_eq!(student["score"], json!(85));
// Evaluate method spec
let method_schema = schema["x-methods"]["passed"].clone();
let check = SchemaValue::new(student.as_value().clone(), method_schema);
assert!(check.validate().is_ok()); // score >= 60SchemaValue::new(data, schema) // construct with schema
SchemaValue::without_schema(data) // construct without schema
sv["key"] // &Value — fast field access
sv.get("key") // Option<SchemaValue> — with sub-schema
sv.set("key", value)? // validated mutation
sv.as_value() // &Value — inner data
sv.into_inner() // Value — consume, drop schema
sv.validate() // Result<(), Vec<ValidationError>>
// Draft-07 logic methods
sv.one_of() // Result<SchemaValue> (XOR)
sv.any_of() // Result<Vec<SchemaValue>> (OR)
sv.all_of() // SchemaValue (AND merged)
sv.not_of(Option<&Value>) // bool
sv.if_then() // SchemaValue (conditional branch)
sv.project() // Result<SchemaValue> (SELECT)
sv.contains(Option<&Value>) // boolOfficial JSON Schema Test Suite: 98.4% (666/677 cases).
Known gaps:
- Float/int equivalence in
const/enum— serde_json limitation (8 cases) $refresolution — not implemented (3 cases)
- Source:
projects/socks5-mesh-rust/schema-value/ - Philosophy:
methodology/sdd-philosophy.md - x- extensions:
methodology/x-extensions.md - Python equivalent:
references/python/schema2object.md