In a web framework, why do constraints declared on a nested object or on list elements often not run during request validation?
answer
- the validator walks the root only
- container rule is not an element rule
- descent must be declared
- keys, values, and the map are three rules
- bound every collection size
basics
~20 sValidation of a bound model stops at its own fields. Reaching a nested object or a collection's elements requires declaring that the validator descends into them; a rule on the container only checks the container.
solid answer
~40 sA validator handed a model evaluates the rules declared **on that model's own fields**. A field holding another object, or a list, is one field: a rule such as `not empty` on a list checks the list, not the entries inside it. Reaching inner values means declaring the traversal — a marker on the reference that says "descend into this", or an element-level declaration saying the rule applies to each entry rather than to the container. Schema-style declarations feel different: the schema describes the whole payload tree, so nesting is covered because it is part of the described shape. The habits that follow are to mark every nested reference you expect checked, to state element rules separately from container rules, and to bound collection size.
code
pseudocode · 6 linesmodel OrderRequest {
customer: Customer rules: [ present, cascade ] # cascade = validate Customer's own fields
items: List<Item> rules: [ size(min=1, max=50) ] # about the LIST
elementRules: [ cascade ] # about each ITEM
note: String rules: [ maxLength(500) ]
}go deeper
Learn the one-line version: a rule on a list field is about the list. Checking what is inside a nested object or a collection has to be declared separately.
Be able to name both declarations — cascade into the reference, element rules on the collection — and explain why the default stops at the root's own fields.
Bring the operational angle: unbounded collections make the validator's own traversal client-controlled work, and a missing cascade is invisible until a payload is legal on top and illegal below.
Weigh a per-reference cascade style against a schema that describes the whole payload: one forgets markers, the other drifts from the model, and the choice sets what a contract review has to inspect.
## The validator walks what it is told to walk A request model is usually a tree: an order carries a customer object, a list of line items, each line item a quantity and a product reference. The validation step is handed the **root** of that tree. What it checks by default is the set of rules declared on the root's own fields. A field whose value is another object is still one field, and a field whose value is a list is still one field. That produces the two classic surprises: 1. **The nested object is never entered.** Its own fields declare perfectly good rules, and none of them run, because nothing told the validator to descend through the reference that points at it. 2. **A rule on a collection field constrains the collection.** `not empty`, `size between 1 and 50` and similar rules describe the container. They say nothing about whether each entry is legal. The fix in marker- and rule-set-style declarations is to say so explicitly: mark the reference as one to cascade into, and declare element rules as element rules rather than as container rules. Frameworks differ in how deep this goes by default — some descend automatically into anything the model declares as a structured type, others descend only where the reference is marked — so the safe habit is to state it rather than to assume it. ## Container rule versus element rule | You want | Declare it | Common mistake | |---|---|---| | The list must not be empty | A container rule on the list field | Assuming it also checks entries | | Every entry must be a legal item | Element-level rule, or cascade into the item type | Declaring the rule once on the field and expecting per-entry checks | | At most 50 entries | A size bound on the list field | Leaving it off entirely | | A nested object's fields are legal | Cascade marker on the reference to it | Re-declaring the inner rules on the outer model | Maps and dictionaries add one more distinction, because they have two element positions: rules can apply to the **keys**, to the **values**, or to the map as a container, and these are three different declarations. ## Why bounding size is a validation concern, not just a business one Cascading is recursive work. Every element of every collection in the tree becomes a unit of validation work that the *client* chose the amount of. A list field with no declared upper bound is an invitation to send a very large one: the body is parsed, bound, and then walked element by element, all before the handler decides it wanted ten entries. Declaring a size bound on every collection field turns that into an early rejection on a cheap check. The same reasoning applies to nesting depth for self-referential models, where a deeply nested or cyclic structure can make the traversal expensive or non-terminating; validators that track visited instances avoid the cycle, but the depth cost remains. ## Where the value shows up in the response Because the validator descends, a failure that occurs three levels down has to identify **which** value failed — a path through the tree rather than a bare field name. That is what makes cascading usable rather than merely thorough: without a path, a caller is told only that something inside the order was wrong. The shape of that reported failure is a separate concern from the traversal, but the traversal is what creates the need for it. ## Practical checklist - Mark every reference to a nested model you expect to be validated; do not assume the default descends. - Write container rules and element rules as separate declarations, and say which one you meant. - Put a size bound on every collection field, including ones you believe are always short. - For maps, decide explicitly whether the rule is about keys, values, or the map itself. - Test with a payload that is legal at the top level and illegal two levels down — that single test is what catches a missing cascade, and it is the test teams skip.
- A list field declares `size between 1 and 20` and every element type declares its own rules. What runs?Only the size check on the list, unless the field also declares that each element is validated. The element type's rules exist but nothing walks into the entries, so a list of twenty illegal items passes.
- How does a schema-style declaration differ here?A schema describes the whole payload tree, so nested objects and array items are validated because the schema says what they must look like, not because a reference was marked. The failure mode moves from a forgotten cascade to a schema that drifts from the model it describes.
- What can go wrong when a model can contain itself, directly or through a chain?Traversal can revisit the same instance or descend very deep. Validators that track visited instances terminate on cycles, but the work still scales with the structure the client sent, so declare a depth or size bound and reject oversized bodies early.
Signing for a sealed parcel confirms that a parcel arrived; it says nothing about the boxes inside. Cascading is the instruction to open them and inspect each one.
saying these in an interview costs you the question
- Believes a not-empty rule on a list also checks each element
- Assumes the validator always walks the whole object graph it is handed
- Re-declares inner rules on the outer model instead of cascading
- Leaves collection fields unbounded because the client is trusted
- Treats map key rules and map value rules as one declaration