Files
gruperly/.agents/skills/zod/references/error-path-for-nested.md
2026-09-04 16:49:24 -03:00

3.6 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use issue.path for Nested Error Location HIGH Without path information, users can't identify which nested field failed; path provides exact location in complex objects error, path, nested, debugging

Use issue.path for Nested Error Location

When validating nested objects or arrays, issue.path tells you exactly where the error occurred. This is essential for highlighting the correct form field or providing precise error messages in complex data structures.

Incorrect (ignoring path information):

import { z } from 'zod'

const orderSchema = z.object({
  customer: z.object({
    name: z.string().min(1, 'Name required'),
    address: z.object({
      street: z.string().min(1, 'Street required'),
      city: z.string().min(1, 'City required'),
    }),
  }),
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number().positive('Quantity must be positive'),
  })),
})

const result = orderSchema.safeParse({
  customer: { name: '', address: { street: '', city: '' } },
  items: [{ productId: 'abc', quantity: -1 }],
})

if (!result.success) {
  // Only showing message, not WHERE the error is
  result.error.issues.forEach(issue => {
    console.log(issue.message)  // 'Name required', 'Street required', 'Quantity must be positive'
    // User: "Which quantity? Which field?"
  })
}

Correct (using path information):

import { z } from 'zod'

const orderSchema = z.object({
  customer: z.object({
    name: z.string().min(1, 'Name required'),
    address: z.object({
      street: z.string().min(1, 'Street required'),
      city: z.string().min(1, 'City required'),
    }),
  }),
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number().positive('Quantity must be positive'),
  })),
})

const result = orderSchema.safeParse({
  customer: { name: '', address: { street: '', city: '' } },
  items: [{ productId: 'abc', quantity: -1 }],
})

if (!result.success) {
  result.error.issues.forEach(issue => {
    // path is an array of keys/indices
    console.log(`${issue.path.join('.')}: ${issue.message}`)
    // 'customer.name: Name required'
    // 'customer.address.street: Street required'
    // 'customer.address.city: City required'
    // 'items.0.quantity: Quantity must be positive'
  })
}

Building field-specific error mapping:

function mapErrorsToFields(error: z.ZodError) {
  const fieldErrors: Map<string, string[]> = new Map()

  for (const issue of error.issues) {
    const fieldPath = issue.path.join('.')
    const existing = fieldErrors.get(fieldPath) ?? []
    fieldErrors.set(fieldPath, [...existing, issue.message])
  }

  return fieldErrors
}

// Usage
const errors = mapErrorsToFields(result.error)
errors.get('customer.name')  // ['Name required']
errors.get('items.0.quantity')  // ['Quantity must be positive']

For array items, get index from path:

const itemsWithErrors: Set<number> = new Set()

result.error.issues.forEach(issue => {
  if (issue.path[0] === 'items' && typeof issue.path[1] === 'number') {
    itemsWithErrors.add(issue.path[1])
  }
})

// Highlight items at indices: Set { 0 }

Using path with format():

const formatted = result.error.format()

// Access errors at any path level
formatted.customer?.address?.city?._errors  // ['City required']
formatted.items?.[0]?.quantity?._errors  // ['Quantity must be positive']

When NOT to use this pattern:

  • Flat objects where field name is obvious
  • When using form libraries that handle path mapping

Reference: Zod Error Handling