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

Angular调用添加商品API返回400错误,请求技术排查

问题定位与解决

问题现象

有一个向数据库添加商品的API,Postman测试正常,但Angular表单提交时返回400错误,后端抛出JSON反序列化异常:

JSON parse error: Cannot deserialize value of type com.example.backendapp.entities.Image from Array value (token JsonToken.START_ARRAY); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type com.example.backendapp.entities.Image from Array value (token JsonToken.START_ARRAY) at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 151] (through reference chain: com.example.backendapp.DTOs.ItemAddDTO["image"])

已核对参数名、API端点,移除图片上传功能仍无效,其他接口运行正常。

代码片段

Angular 代码

Service 方法

addItem(item:FormData):Observable<any>{ //itemAdd
    return this.http.post<ItemAdd>(this.url+this.endpoint,item);
}

表单与Item定义

addItemForm = new FormGroup({
    brand: new FormControl(''),
    name: new FormControl(''),
    serialNumber: new FormControl(''),
    price: new FormControl(''),
    generalCategory: new FormControl(''),
    category: new FormControl(''),
    descriere: new FormControl(''),
    quantity: new FormControl(''),
    percentageSale: new FormControl(''),
    image: new FormControl('')
});
item: ItemAdd = {
    brand: "",
    name: "",
    serialNumber: "",
    price: "",
    generalCategory: "",
    category: "",
    descriere: "",
    quantity: "",
    percentSale: "",
    image: [] // 数组类型
}

添加商品函数

addItem() {
    this.item.brand = this.addItemForm.value.brand;
    this.item.name = this.addItemForm.value.name;
    this.item.serialNumber = this.addItemForm.value.serialNumber;
    this.item.price = this.addItemForm.value.price;
    this.item.generalCategory = this.addItemForm.value.generalCategory;
    this.item.category = this.addItemForm.value.category;
    this.item.descriere = this.addItemForm.value.descriere;
    this.item.quantity = this.addItemForm.value.quantity;
    this.item.percentSale = this.addItemForm.value.percentageSale;
    const itemFormData = this.createFormData(this.item)
    this.dialService.addItem(itemFormData).subscribe(response => {
        this.dialogRef.close();
        this.toast.showToast("Item succesfully created", "info");
        console.log("item added")
        console.log(response);
    });
}

FormData 创建函数

createFormData(item: ItemAdd): FormData {
    const formData = new FormData();
    formData.append('item',
        new Blob([JSON.stringify(item)], {type: 'application/json'}));
    for (var image = 0; image < item.image.length; image++) {
        formData.append('image',
            item.image[image].file,
            item.image[image].file.name)
    }
    return formData;
}

请求与响应头

请求头

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ro-RO,ro;q=0.9,en-US;q=0.8,en;q=0.7
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbkB5YWhvby5jb20iLCJpYXQiOjE2ODA5NDk0ODcsImV4cCI6MTY4MTEyMjI4N30.nZgaAbjFRYZNZFsfHGoA9DrTLCUuC4N26q5VtXovS1g
Connection: keep-alive
Content-Length: 473142
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryw60j7sVWdlQukUvr
Host: localhost:8081
Origin: http://localhost:4200
Referer: http://localhost:4200/
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36

响应头

access-control-allow-methods: PUT, GET, HEAD, POST, DELETE, OPTIONS
access-control-allow-origin: http://localhost:4200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: close
Content-Type: application/json
Date: Sat, 08 Apr 2023 13:25:15 GMT
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

后端代码

Controller 方法

@PostMapping("/items")
public Item createItem(@RequestPart("item") ItemAddDTO item,@RequestPart("image") MultipartFile file) throws IOException {
    return itemService.saveItem(item,file);
}

Service 方法

public Item saveItem(ItemAddDTO itemAddDTO,MultipartFile file) throws IOException {
    Item item = this.modelMapper.map(itemAddDTO, Item.class);
    item.setDateOfAdd(new Date());
    item.setImage(imageService.uploadImageToFile(file));
    itemRepository.save(item);
    return item;
}

问题根源

后端ItemAddDTO的image字段是单个Image对象类型,但前端ItemAdd的image是数组类型,且序列化item为JSON时把空数组[]传了过去。后端Jackson尝试将数组反序列化为单个对象,导致类型不匹配,抛出400错误。

另外,图片已经通过单独的RequestPart传递,不需要在item的JSON中包含image字段。

解决方案

修改前端代码,移除ItemAdd中的image字段,或者在序列化时排除该字段:

方案1:修改ItemAdd定义

item: ItemAdd = {
    brand: "",
    name: "",
    serialNumber: "",
    price: "",
    generalCategory: "",
    category: "",
    descriere: "",
    quantity: "",
    percentSale: ""
    // 移除image字段
}

方案2:序列化时排除image字段

修改createFormData函数,序列化时忽略image:

createFormData(item: ItemAdd): FormData {
    const formData = new FormData();
    // 深拷贝item并删除image属性
    const itemWithoutImage = {...item};
    delete itemWithoutImage.image;
    formData.append('item',
        new Blob([JSON.stringify(itemWithoutImage)], {type: 'application/json'}));
    for (var image = 0; image < item.image.length; image++) {
        formData.append('image',
            item.image[image].file,
            item.image[image].file.name)
    }
    return formData;
}

这样后端就能正确解析ItemAddDTO,不会出现类型不匹配的问题。

内容的提问来源于stack exchange,提问作者DanM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:37:18