Vue3+VeeValidate+Zod:空对象metadata文本框显示异常问题
Vue3 + VeeValidate + Zod 解决metadata空对象在textarea显示[object Object]的问题
问题场景
在Vue3项目中使用VeeValidate和Zod Schema构建商品分类编辑表单,metadata字段通过textarea接受JSON输入。当metadata为空对象{}时,textarea显示[object Object],而非预期的格式化字符串{}。
- 接口返回的JSON负载:
{ "metadata": {} }
- 预期textarea显示:
{} - 实际显示:
[object Object]
核心代码
EditCategoryForm.vue
<script setup lang="ts"> import { useForm } from 'vee-validate'; import { toTypedSchema } from '@vee-validate/zod'; import { editProductCategorySchema } from '@/types'; import type { ProductCategoriesRecord } from '@/types/pocketbase-types'; const props = defineProps<{ initialData: ProductCategoriesRecord }>(); const emit = defineEmits<{ (e: 'submit', payload: ProductCategoriesRecord): void }>(); const formSchema = toTypedSchema(editProductCategorySchema); const { handleSubmit } = useForm({ validationSchema: formSchema, initialValues: { name: props.initialData.name || '', description: props.initialData.description || '', handle: props.initialData.handle || '', metadata: JSON.stringify(props.initialData.metadata ?? {}, null, 2), }, }); const onSubmit = handleSubmit((values) => { const updatedPayload: ProductCategoriesRecord = { ...props.initialData, ...values, }; emit('submit', updatedPayload); }); </script> <template> <form @submit="onSubmit"> <FormField v-slot="{ componentField }" name="metadata"> <FormItem> <FormLabel>Metadata</FormLabel> <FormControl> <Textarea v-bind="componentField" placeholder='{ "key": "value" }' /> </FormControl> <FormMessage /> </FormItem> </FormField> <Button type="submit">Update</Button> </form> </template>
Zod Schema定义
import { z } from 'zod'; import { zu } from 'zod_utilz'; const productCategoryBaseSchema = z.object({ name: z.string().min(1, 'Name is required'), handle: z.string().min(1, 'Handle is required'), description: z.string().optional().nullable(), }); export const editProductCategorySchema = productCategoryBaseSchema.extend({ metadata: zu.stringToJSON().optional().nullable(), });
原因分析
问题根源在于zod_utilz的zu.stringToJSON()转换器:它会自动将表单输入的JSON字符串解析为JavaScript对象。尽管我们在initialValues中用JSON.stringify()把空对象转成了"{}"字符串,但经过Schema转换后,表单的model值会被转换成空对象{}。而textarea绑定对象时,会默认调用toString()方法,最终显示[object Object]。
解决方案
方法1:单独处理metadata字段的显示与转换
通过VeeValidate的useField为metadata字段配置视图与模型的双向转换逻辑,确保显示时用格式化JSON字符串,验证/提交时用JavaScript对象:
<script setup lang="ts"> import { useForm, useField } from 'vee-validate'; import { toTypedSchema } from '@vee-validate/zod'; import { editProductCategorySchema } from '@/types'; import type { ProductCategoriesRecord } from '@/types/pocketbase-types'; const props = defineProps<{ initialData: ProductCategoriesRecord }>(); const emit = defineEmits<{ (e: 'submit', payload: ProductCategoriesRecord): void }>(); const formSchema = toTypedSchema(editProductCategorySchema); const { handleSubmit } = useForm({ validationSchema: formSchema, initialValues: { name: props.initialData.name || '', description: props.initialData.description || '', handle: props.initialData.handle || '', }, }); // 单独处理metadata字段的转换逻辑 const { value: metadataValue } = useField('metadata', { initialValue: JSON.stringify(props.initialData.metadata ?? {}, null, 2), transform: { // 将用户输入的字符串转为对象,供Schema验证 toModel: (val) => { if (!val) return {}; try { return JSON.parse(val); } catch { return val; // 保留原始值,让Schema验证报错 } }, // 将对象转为格式化字符串,用于textarea显示 toView: (val) => { return typeof val === 'object' ? JSON.stringify(val, null, 2) : val; }, }, }); const onSubmit = handleSubmit((values) => { const updatedPayload: ProductCategoriesRecord = { ...props.initialData, ...values, }; emit('submit', updatedPayload); }); </script> <template> <form @submit="onSubmit"> <FormItem> <FormLabel>Metadata</FormLabel> <FormControl> <Textarea v-model="metadataValue" placeholder='{ "key": "value" }' /> </FormControl> <FormMessage name="metadata" /> </FormItem> <Button type="submit">Update</Button> </form> </template>
方法2:调整Schema,保持字段为字符串类型(提交时手动解析)
如果不需要Schema自动转换为对象,可以修改Schema仅验证JSON格式的字符串,提交时手动解析为对象:
修改Zod Schema
const productCategoryBaseSchema = z.object({ name: z.string().min(1, 'Name is required'), handle: z.string().min(1, 'Handle is required'), description: z.string().optional().nullable(), }); export const editProductCategorySchema = productCategoryBaseSchema.extend({ metadata: z.string() .refine((val) => { if (!val) return true; // 允许空值 try { JSON.parse(val); return true; } catch { return false; } }, 'Invalid JSON format') .optional() .nullable(), });
修改表单提交逻辑
<script setup lang="ts"> // ... 其他代码不变 const onSubmit = handleSubmit((values) => { const updatedPayload: ProductCategoriesRecord = { ...props.initialData, ...values, // 提交时将字符串解析为对象 metadata: values.metadata ? JSON.parse(values.metadata) : {}, }; emit('submit', updatedPayload); }); </script>
这样表单的metadata字段始终存储字符串,textarea会正确显示格式化的JSON,包括空对象{}。
内容的提问来源于stack exchange,提问作者Rasik
相关产品推荐
相关产品推荐

