---
title: "Object 对象"
description: "afz.object() 的调用方式、嵌套结构与额外字段处理策略。"
seo_title: "Object Field"
seo_description: "How a Zod object field maps to AutoForm controls — direct and curried calls, nested objects, typed objects and extra-key strategies (object, looseObject, strictObject)."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/auto-form/object"
---
# Object 对象

> afz.object() 的调用方式、嵌套结构与额外字段处理策略。

> \[\!NOTE\]
> 
> 两种调用方式的区别
> 
> ```ts
> // 直接调用
> afz.object(shape, meta?)
> 
> // 柯里化调用（类型参数 `<T>` 仅用于 IDE 提示，不影响运行时验证。）
> afz.object<T>()(shape, meta?)
> ```
> 
> ```ts
> const schema = afz.object({
>   name: afz.string(),
>   age: afz.number()
> })
> ```
> 
> ```ts
> interface UserInfo {
>   name: string
>   age: number
>   email: string
> }
> 
> const schema = afz.object<UserInfo>()({
>   name: afz.string(),  // IDE 会提示 name | age | email
>   email: afz.email()
> })
> ```

## 通用对象

通用对象类型通常与 `selectMenu` 结合使用，用于选择复杂数据结构：

```vue [AutoFormFieldObjectBasicExample.vue]
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const { data: users, execute, pending } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'typicode-users-basic',
  immediate: false,
  transform: (data: { id: number, name: string }[]) => {
    return data?.map(user => ({
      label: user.name,
      value: String(user.id),
      avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, loading: 'lazy' as const }
    }))
  }
})

const schema = afz.object({
  name: afz.string().min(1, '请输入姓名').default('张三'),
  age: afz.number().min(1).max(150).default(30),
  email: afz.email('请输入有效的邮箱地址').default('zhangsan@example.com'),
  user: afz.object({
    label: afz.string(),
    value: afz.string(),
    avatar: afz.object({ src: afz.url() })
  }, {
    type: 'selectMenu',
    controlProps: ({ value }: { value: { avatar: { src: string } } }) => ({
      'placeholder': '请选择用户',
      'items': users.value || [],
      'avatar': value?.avatar,
      'loading': pending.value,
      'onUpdate:open': () => {
        if (!users.value && !pending.value) {
          execute()
        }
      }
    })
  })
})

async function onSubmit(event: FormSubmitEvent<z.output<typeof schema>>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" @submit="onSubmit" />
</template>
```

## 嵌套对象

嵌套对象结构，支持折叠展开：

```vue [AutoFormFieldObjectNestedExample.vue]
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  user: afz.object({
    name: afz.string(),
    email: afz.email()
  }).meta({ label: '用户信息', collapsible: { defaultOpen: true } }),
  address: afz.object({
    street: afz.string(),
    city: afz.string(),
    zipCode: afz.string()
  }).optional().meta({ label: '地址信息' })
})

async function onSubmit(event: FormSubmitEvent<z.output<typeof schema>>) {
  toast.add({
    title: 'Success',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" @submit="onSubmit" />
</template>
```

## 类型化对象

> \[\!TIP\]
> 
> 使用 TypeScript 接口约束对象结构，在定义 schema 时获得字段名的 IDE 提示：

```vue [AutoFormFieldObjectTypedExample.vue]
<script lang="ts" setup>
const { afz } = useAutoForm()
const toast = useToast()

interface UserInfo {
  name: string
  age: number
  email: string
}

const userSchema = {
  name: afz.string().min(1, '请输入姓名').default('张三'),
  age: afz.number().min(1).max(150).default(18),
  email: afz.email('请输入有效的邮箱地址').default('example@example.com')
}

const testUser = {
  name: '张三',
  age: 28,
  email: 'test@example.com',
  extraField: '我是额外字段'
}

const normalSchema = afz.object<UserInfo>()(userSchema)
const looseSchema = afz.looseObject<UserInfo>()(userSchema)
const strictSchema = afz.strictObject<UserInfo>()(userSchema)

const currentType = ref<'normal' | 'loose' | 'strict'>('normal')
const items = [
  { label: 'Normal', value: 'normal' },
  { label: 'Loose', value: 'loose' },
  { label: 'Strict', value: 'strict' }
]

const schema = computed(() => {
  switch (currentType.value) {
    case 'loose': return looseSchema
    case 'strict': return strictSchema
    default: return normalSchema
  }
})

async function click() {
  const parsed = schema.value.safeParse(testUser)
  if (!parsed.success) {
    toast.add({
      title: '验证失败',
      color: 'error',
      description: JSON.stringify(parsed.error.issues, null, 2)
    })
  } else {
    toast.add({
      title: '验证成功',
      color: 'success',
      description: JSON.stringify(parsed.data, null, 2)
    })
  }
}
</script>

<template>
  <UCard class="space-y-4">
    <template #header>
      <div class="flex items-center gap-2">
        <span class="text-sm font-medium">选择对象类型：</span>
        <USelect v-model="currentType" :items="items" />
      </div>
    </template>

    <MAutoForm :schema="schema" :submit="false">
      <template #footer>
        <UButton color="primary" @click="click">
          验证类型
        </UButton>
      </template>
    </MAutoForm>
  </UCard>
</template>
```

> \[\!NOTE\]
> 
> 三种对象工厂对应不同的额外字段处理策略：
> 
> ```ts
> const schema = afz.object({ name: afz.string() })
> 
> schema.parse({ name: '张三', extra: 'removed' })
> // 结果：{ name: '张三' }
> ```
> 
> ```ts
> const schema = afz.looseObject({ name: afz.string() })
> 
> schema.parse({ name: '张三', extra: 'kept' })
> // 结果：{ name: '张三', extra: 'kept' }
> ```
> 
> ```ts
> const schema = afz.strictObject({ name: afz.string() })
> 
> schema.parse({ name: '张三', extra: 'error' })
> // 错误：Unrecognized key(s) in object: 'extra'
> ```


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
