You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 01:35:56