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

MethodEffect
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

MethodUnknown 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

MethodEffect
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.