Object
Invocation methods, nested structures and extra key handling strategies for afz.object().
Difference between two invocation styles
// Direct call
afz.object(shape, meta?)
// Curried call (type parameter `<T>` is for IDE hints only, does not affect runtime validation)
afz.object<T>()(shape, meta?)
const schema = afz.object({
name: afz.string(),
age: afz.number()
})
interface UserInfo {
name: string
age: number
email: string
}
const schema = afz.object<UserInfo>()({
name: afz.string(), // IDE will suggest name | age | email
email: afz.email()
})
Generic Object
Generic object types are typically combined with selectMenu for selecting complex data structures:
<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>
Nested Object
Nested object structure with collapsible/expandable support:
<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>
Typed Object
Use a TypeScript interface to constrain the object structure, getting IDE field name suggestions when defining the schema:
选择对象类型:
<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>
Three object factories correspond to different extra key handling strategies:
const schema = afz.object({ name: afz.string() })
schema.parse({ name: 'John', extra: 'removed' })
// Result: { name: 'John' }
const schema = afz.looseObject({ name: afz.string() })
schema.parse({ name: 'John', extra: 'kept' })
// Result: { name: 'John', extra: 'kept' }
const schema = afz.strictObject({ name: afz.string() })
schema.parse({ name: 'John', extra: 'error' })
// Error: Unrecognized key(s) in object: 'extra'