Schemas
Dynamic objects
vld::object() — runtime-defined objects with unknown-key modes, catchall, partial, pick/omit, extend/merge and conditional rules.
vld::object() builds an object schema at runtime. Its output is a serde_json::Map<String, Value> with the validated (and transformed) field values.
For typed structs use
schema! or #[derive(Validate)]. Use vld::object() when the fields are only known at runtime (from config, a database, a plugin) or when you need the operations below.use vld::prelude::*;
let user = vld::object()
.field("name", vld::string().min(1))
.field("age", vld::number().int().min(0))
.field_optional("nickname", vld::string().min(1));
let out = user.parse(r#"{"name": "Ada", "age": 36, "extra": true}"#).unwrap();
assert_eq!(out["name"], "Ada");
assert_eq!(out["nickname"], serde_json::Value::Null);
assert!(out.get("extra").is_none()); // stripped
Fields
| Method | Effect |
|---|---|
field(name, schema) | required field (a field with the same name is replaced) |
field_optional(name, schema) | missing or null gives null in the output; the inner schema still runs first, so .with_default() and .catch() produce their value |
keyof() | the declared field names |
A required field that is absent reports missing_field; a present null reports invalid_type.
Unknown keys
| Method | Unknown keys are |
|---|---|
strip() (default) | dropped from the output |
strict() | rejected with unrecognized_field |
passthrough() | copied to the output unchanged |
catchall(schema) | validated with schema and kept (overrides the mode) |
let strict = vld::object().field("id", vld::u64()).strict();
assert!(strict.parse(r#"{"id": 1, "x": 2}"#).is_err());
let labels = vld::object().field("id", vld::u64()).catchall(vld::string());
assert!(labels.parse(r#"{"id": 1, "env": "prod"}"#).is_ok());
assert!(labels.parse(r#"{"id": 1, "env": 3}"#).is_err());
Composition
| Method | Effect |
|---|---|
pick(&["a", "b"]) | keep only these fields |
omit("a") | remove one field |
extend(other) / merge(other) | add the fields of other; on a name clash other wins. other's non-default unknown-key mode, catchall, min_keys / max_keys and when rules are taken too |
partial() | every field becomes optional (missing / null → null); not recursive: call partial() on nested objects before adding them |
required() | the opposite of partial(): null fails again |
min_keys(n) / max_keys(n) | bound the number of keys in the input |
let base = vld::object().field("id", vld::u64()).field("name", vld::string());
let with_email = vld::object().field("email", vld::string().email());
let create = base.extend(with_email).omit("id");
let patch = vld::object()
.field("name", vld::string())
.field("email", vld::string().email())
.partial();
assert!(create.parse(r#"{"name": "Ada", "email": "ada@example.com"}"#).is_ok());
assert!(patch.parse(r#"{}"#).is_ok());
Conditional rules
when(field, value, target, schema) validates target with an extra schema when field equals value. The rule runs in addition to the target's own field schema. Under strict() an undeclared target is not reported as unknown.
let account = vld::object()
.field("role", vld::string())
.field_optional("admin_key", vld::string())
.when("role", "admin", "admin_key", vld::string().min(10));
assert!(account.parse(r#"{"role": "user"}"#).is_ok());
assert!(account.parse(r#"{"role": "admin", "admin_key": "short"}"#).is_err());
JSON Schema
With the openapi feature every field keeps its JSON Schema and required status (.optional(), .nullish(), .with_default() and .nullable() fields are not required), so user.json_schema() describes the object fully.