FormData 不会把 JavaScript 对象数组自动序列化为 Java List。直接 append 对象时,常见结果是字符串 [object Object];即使先 JSON.stringify,后端也必须按同一契约反序列化,不能指望普通表单绑定自动解析这段 JSON。

先明确字段传的是什么

假设列表是:

const items = [{ name: "示例 A", count: 2 }, { name: "示例 B", count: 1 }];
const form = new FormData();
form.append("itemsJson", JSON.stringify(items));

itemsJson 是一个包含 JSON 数组文本的表单字段,不是多个 JavaBean 字段。FormData.append 文档说明非字符串、非 Blob 的值会转成字符串,这也是直接传对象产生问题的原因。

提交时不要手动写不带 boundary 的 multipart/form-data 请求头,由浏览器构造:

async function submitItems(items) {
    const form = new FormData();
    form.append("itemsJson", JSON.stringify(items));
    const response = await fetch("/items", {
        method: "POST",
        body: form
    });
    if (!response.ok) throw new Error("提交失败");
    return response.json();
}

函数只是说明请求契约,项目仍需按自己的认证、CSRF 和错误协议补齐调用条件。

Spring 端显式读取这个 JSON 字段

旧代码使用过 Fastjson.parseArray。下面用 Spring Boot 常见的 Jackson 组件表达同一件事,适用于有 Jackson 和 Spring MVC 的工程:

import java.io.IOException;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ItemsController {
    private final ObjectMapper mapper;
    public ItemsController(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @PostMapping("/items")
    public List<Item> items(@RequestParam("itemsJson") String itemsJson)
            throws IOException {
        List<Item> items = mapper.readValue(
                itemsJson, new TypeReference<List<Item>>() {});
        if (items == null || items.size() > 100) {
            throw new IllegalArgumentException("列表大小不符合要求");
        }
        for (Item item : items) {
            if (item == null || item.name == null ||
                    item.name.trim().isEmpty() || item.count <= 0) {
                throw new IllegalArgumentException("条目不符合要求");
            }
        }
        return items;
    }

    public static class Item {
        public String name;
        public int count;
    }
}

示例返回解析结果供观察,不包含数据库写入。实际应用应限制请求和字段长度,按统一异常协议将无效 JSON 与校验失败转换为客户端错误,不直接使用未校验的数量、身份或价格。

有文件和没有文件时可以选不同协议

没有文件时,直接 application/json 搭配 @RequestBody 往往更简单。有文件时,也可使用 application/json 的 Blob part,后端用 @RequestPart 交给 JSON 转换器;这与本文的“字符串字段再显式解析”是两套契约,前后端应选择同一套。Spring MVC multipart 说明

第一次成功、第二次失败时,还要检查是否复用了 FormData 并重复 append,详见重复表单字段与 JSON 错误。