skip to content

When a constraint fails deep inside a nested object or a list element, how is the field path built and where does it mislead the caller?

level: seniorimportance: should knowfreq 52%

answer

  1. one segment per descent step
  2. index for element, key for entry
  3. container rule versus element rule
  4. model names are not wire names
  5. position means position as validated

basics

~20 s

The engine composes a segment per step as it descends — property names, indexes for list elements, keys for keyed entries — producing paths like items[2].quantity. It misleads because those are model property names, not the names the client actually sent.

solid answer

~40 s

A validation engine walks the model graph and appends one segment per step, so a rule on the third line's quantity fails at `items[2].quantity`, while a size rule on the list itself fails at `items`. Three things then mislead a caller. The path carries **model** property names, and if serialization renames or recases properties the client cannot match it to its own form — the converter must translate each segment through the same naming rules the mapper uses. An index means the position **as validated**, so filtering or sorting during binding breaks it, and unordered collections have no stable index. And collapsing the container rule and the element rule into one path makes a client highlight a whole section for a single bad value.

code

pseudocode · 10 lines
pseudocode
body = {
  "customer": { "email": "not-an-email" },
  "items": [ { "qty": 0 }, { "qty": 5 } ]
}

violations = [
  { path: ["customer", "email"],      rule: "pattern" },
  { path: ["items", 0, "qty"],        rule: "min", params: { min: 1 } },
  { path: ["items"],                  rule: "size", params: { min: 3 } }
]

go deeper

for a junior

Read a path as directions through the payload: a property name per level, brackets for a position in a list. That is how the response points at one input among many.

for a middle

Explain composition during descent, and the difference between a rule on the collection and a rule on one element. Say why the two must not be reported at the same path.

for a senior

Lead with the model-to-wire naming mismatch and how you found it: client highlighting silently doing nothing while every server test passed. Then cover index stability for collections the server tidies.

for a principal

Decide the locator contract once: whether positions, identifiers or both are published, how segments are escaped, and who owns keeping the path vocabulary aligned with the serialized shape.

## How the path is built A validation engine validates a model graph, not a flat record. It descends: for each property it checks the rules declared there, and where a property is itself a model, or a collection of them, it walks into it and keeps going. As it descends it composes a path, appending one segment per step. The composed segments are typically: - a **property name** for a plain step: `customer` - an **index** for a positional collection element: `items[2]` - a **key** for a keyed collection entry: `attributes[colour]` - the **container's own path** when the rule is on the collection rather than on an element: a "at least one item" rule fails at `items`, not at `items[0]` So a rule on the quantity of the third ordered line fails at `items[2].quantity`, and a caller who can read that path can highlight exactly one input control out of fifty. ## Where it misleads the caller ### The names are model names, not wire names This is the defect that ships most often. The path is composed from the **property names of the typed model**. The body the client sent used whatever names the serializer maps to — a different casing convention, an explicitly renamed property, a flattened nested structure. A client searching its own form for `shippingAddress.postCode` finds nothing when it sent `shipping_address.post_code`. The fix is mechanical but must be deliberate: the converter translates each segment through **the same naming rules the mapper used**, rather than emitting model names and hoping. Where a property was renamed individually, the translation has to consult that rename, which is why teams that care keep model and wire names identical and let one naming strategy do the rest. ### Indexes are only as stable as the collection An index means "the element at this position **in what was validated**". If the server filtered, sorted or deduplicated the collection during binding, position *n* on the server is not position *n* in the caller's form. For unordered collections there is no meaningful index at all, and engines fall back on an arbitrary position or an element identifier. Where elements carry a natural identifier, reporting it alongside the index gives the caller something that survives reordering. ### Container and element rules look alike and are not `items` failing a size rule means "send a different number of lines"; `items[0].quantity` failing means "fix this one number". Flattening both into "items has an error" makes the client highlight the whole section for a single bad field, which callers read as the API not knowing what it wants. ### Segment syntax is not free text Any path syntax has to survive names that contain its own separators — a keyed entry whose key holds a dot or a bracket will break a naive split. The converter should emit segments the client can parse unambiguously, either by escaping them or by carrying the path as an ordered list of segments and letting the published format be assembled from that. ## A practical rule for the converter 1. Build from the engine's structural path, never by string-concatenating in the handler. 2. Translate each segment into wire naming, using the mapper's own rules. 3. Preserve the container/element distinction rather than collapsing it. 4. Keep a machine-readable structure available, and derive whatever textual form the contract publishes. 5. Test with a deliberately nasty payload: a nested model inside the second element of a list inside another model. Shallow tests rarely catch the naming mismatch, because at depth one the names usually coincide. ## What the caller actually does with a path Worth stating plainly, because it sets the bar for correctness. A client receives the error body and, for each entry, looks up the input control registered under that path and attaches the message to it. That lookup is an exact match against the names the client itself used when it built the request. It does not guess, it does not fuzzy-match, and when the lookup misses, most clients fall back to showing a generic banner — or to showing nothing. So a path that is merely *nearly* right produces the worst outcome available: the server did the work of finding every failure, the response carries all of it, and the user sees "something went wrong". Nothing in the server's logs records that the caller could not use the answer, which is why this class of bug survives for months. The reliable signals are a client-side metric counting unmatched paths and a contract test that asserts the exact path strings for a deliberately nested payload. ## Why this is a senior question Every part of it works perfectly in a demo with one flat model and fails in production against a real payload: nested structures, renamed properties, lists the server tidies before validating. The candidate who has lived through it names the model-to-wire naming mismatch immediately, because it is the bug that made a client's field highlighting silently do nothing while every server-side test stayed green.

  • How do you keep model paths and wire paths in agreement?
    Run each segment through the same naming rules the serializer applies, so a recasing or an explicit rename is reflected in the path. Teams that want to stop thinking about it keep model property names identical to wire names and let one naming strategy handle the rest uniformly.
  • What is a better locator than an index for elements the caller can reorder?
    A natural identifier carried in the element itself, reported alongside the position. The index says where the server looked; the identifier says which element it was, and only the identifier survives the client sorting, filtering or re-rendering the list before showing the error.
  • Why does a shallow test suite miss the naming problem entirely?
    Because at depth one the model name and the wire name usually coincide, so the path looks right. The mismatch appears with nesting, renames and collections, which is why the regression test should use a nested model inside the second element of a list.
  • What breaks a path when a keyed collection is involved?
    A key containing the syntax's own separators. A key with a dot or a bracket makes a naive split produce the wrong segments, so the converter should escape segments or keep the path as an ordered list and assemble the published textual form from it.

A seat number only helps if the boarding pass uses the same numbering. Reporting the server model's row and seat to a caller whose form numbers them differently points confidently at the wrong chair.

saying these in an interview costs you the question

  • Assumes the model property path is already what the client sent
  • Builds paths by concatenating strings inside the handler
  • Reports a container rule at an element position, or the reverse
  • Treats a collection index as stable when the server reorders before validating
  • Splits a path on dots without considering keys that contain them