如何以 POST 方法在 REST API 中发送日期

Posted

技术标签:

【中文标题】如何以 POST 方法在 REST API 中发送日期【英文标题】:How to send Date in REST API in POST method 【发布时间】:2018-01-21 23:06:21 【问题描述】:

我正在尝试构建带有 Spring 支持的 RESTful Web 服务。尝试发送 POST 请求时出现以下异常。

输入:

POST    http://localhost:8080/InventoryDemo/item

在 JSON 负载中:

"materialId":"ID02","materialName":"Material_2","materialCategory":"LIQUID","currency":"RUPEES","unitCostInCurrency":2200.0,"quantityLevel":1000,"quantityAtDate":"2016-04-11","warehouseName":"WareHouse_2"

例外:

WARNING: Handler execution resulted in exception: Could not read document: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-04-11'); no single-String constructor/factory method
 at [Source: java.io.PushbackInputStream@378ace07; line: 1, column: 146] (through reference chain: com.psl.inventory.model.InventorySystemModel["quantityAtDate"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-04-11'); no single-String constructor/factory method
 at [Source: java.io.PushbackInputStream@378ace07; line: 1, column: 146] (through reference chain: com.psl.inventory.model.InventorySystemModel["quantityAtDate"])

这是我来自 @RestController 的 POST 方法:

@RequestMapping(value = "/item", method = RequestMethod.POST)
    public ResponseEntity<Void> createInventorySystemModel(@RequestBody InventorySystemModel inventorySystemModel,  UriComponentsBuilder ucBuilder) 
        System.out.println("Creating InventorySystemModel " + inventorySystemModel.getMaterialName());

        if (inventorySystemService.isInventorySystemModelExist(inventorySystemModel)) 
            System.out.println("A InventorySystemModel with name " + inventorySystemModel.getMaterialName() + " already exist");
            return new ResponseEntity<Void>(HttpStatus.CONFLICT);
        

        inventorySystemService.saveInventoryItem(inventorySystemModel);

        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(ucBuilder.path("/user/materialId").buildAndExpand(inventorySystemModel.getMaterialId()).toUri());
        return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
    

这是我的 POJO 类:

public class InventorySystemModel 

    private String materialId;
    private String materialName;
    private String materialCategory;
    private String currency;
    private double unitCostInCurrency;
    private int quantityLevel;
    private LocalDate quantityAtDate;
    private String warehouseName;

    public InventorySystemModel()

    
    public InventorySystemModel(String materialId, String materialName,
            String materialCategory, String currency,
            double unitCostInCurrency, int quantityLevel, LocalDate quantityAtDate,
            String warehouseName) 
        super();
        this.materialId = materialId;
        this.materialName = materialName;
        this.materialCategory = materialCategory;
        this.currency = currency;
        this.unitCostInCurrency = unitCostInCurrency;
        this.quantityLevel = quantityLevel;
        this.quantityAtDate = quantityAtDate;
        this.warehouseName = warehouseName;
    

    public String getMaterialId() 
        return materialId;
    
    public void setMaterialId(String materialId) 
        this.materialId = materialId;
    
    public String getMaterialName() 
        return materialName;
    
    public void setMaterialName(String materialName) 
        this.materialName = materialName;
    
    public String getMaterialCategory() 
        return materialCategory;
    
    public void setMaterialCategory(String materialCategory) 
        this.materialCategory = materialCategory;
    
    public String getCurrency() 
        return currency;
    
    public void setCurrency(String currency) 
        this.currency = currency;
    
    public double getUnitCostInCurrency() 
        return unitCostInCurrency;
    
    public void setUnitCostInCurrency(double unitCostInCurrency) 
        this.unitCostInCurrency = unitCostInCurrency;
    
    public int getQuantityLevel() 
        return quantityLevel;
    
    public void setQuantityLevel(int quantityLevel) 
        this.quantityLevel = quantityLevel;
    
    public LocalDate getQuantityAtDate() 
        return quantityAtDate;
    
    public void setQuantityAtDate(LocalDate quantityAtDate) 
        this.quantityAtDate = quantityAtDate;
    
    public String getWarehouseName() 
        return warehouseName;
    
    public void setWarehouseName(String warehouseName) 
        this.warehouseName = warehouseName;
    
    @Override
    public int hashCode() 
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((currency == null) ? 0 : currency.hashCode());
        result = prime
                * result
                + ((materialCategory == null) ? 0 : materialCategory.hashCode());
        result = prime * result
                + ((materialId == null) ? 0 : materialId.hashCode());
        result = prime * result
                + ((materialName == null) ? 0 : materialName.hashCode());
        result = prime * result
                + ((quantityAtDate == null) ? 0 : quantityAtDate.hashCode());
        result = prime * result + quantityLevel;
        long temp;
        temp = Double.doubleToLongBits(unitCostInCurrency);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        result = prime * result
                + ((warehouseName == null) ? 0 : warehouseName.hashCode());
        return result;
    
    @Override
    public boolean equals(Object obj) 
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        InventorySystemModel other = (InventorySystemModel) obj;
        if (currency == null) 
            if (other.currency != null)
                return false;
         else if (!currency.equals(other.currency))
            return false;
        if (materialCategory == null) 
            if (other.materialCategory != null)
                return false;
         else if (!materialCategory.equals(other.materialCategory))
            return false;
        if (materialId == null) 
            if (other.materialId != null)
                return false;
         else if (!materialId.equals(other.materialId))
            return false;
        if (materialName == null) 
            if (other.materialName != null)
                return false;
         else if (!materialName.equals(other.materialName))
            return false;
        if (quantityAtDate == null) 
            if (other.quantityAtDate != null)
                return false;
         else if (!quantityAtDate.equals(other.quantityAtDate))
            return false;
        if (quantityLevel != other.quantityLevel)
            return false;
        if (Double.doubleToLongBits(unitCostInCurrency) != Double
                .doubleToLongBits(other.unitCostInCurrency))
            return false;
        if (warehouseName == null) 
            if (other.warehouseName != null)
                return false;
         else if (!warehouseName.equals(other.warehouseName))
            return false;
        return true;
    
    @Override
    public String toString() 
        return "InventorySystemModel [materialId=" + materialId
                + ", materialName=" + materialName + ", materialCategory="
                + materialCategory + ", currency=" + currency
                + ", unitCostInCurrency=" + unitCostInCurrency
                + ", quantityLevel=" + quantityLevel + ", quantityAtDate="
                + quantityAtDate + ", warehouseName=" + warehouseName + "]";
    

仅供参考:我确实检查了this 的帖子,但没有得到关于我需要在哪里进行修改的线索。

我正在使用 Java 8 和 Spring 4.2

有人可以详细解释一下我到底需要在这里做什么。 我还想要相同的日期格式,当我点击 GET 请求时。

谢谢。

【问题讨论】:

拥有 public void setQuantityAtDate(String quantityAtDate) 并使用 dateFormatter 获取字符串输入并将其转换为 Date 不是更容易吗? 这个问题和这个***.com/questions/29571587/…比较相似 【参考方案1】:

使用反序列化器来解析 LocalDate。

添加 Maven 依赖 -

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.8.10</version>
    </dependency>

如果你的 RESTful 服务是直接解析 bean,添加下面

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

@PostMapping(value = "/xyz")
@JsonDeserialize(using = LocalDateDeserializer.class)
public  ResponseEntity <String> testMethod (@RequestBody Bean bean)

否则,在 bean 类中添加 deserializer

【讨论】:

【参考方案2】:

错误

JsonMappingException 是 Java 的 JSON 解析器 Jackson 抛出的异常。它表示将 JSON 映射到 Java bean 时出现致命问题。

在这种情况下,字符串 2016-04-11 似乎无法从 Java 8 解析为 LocalDate

如何解决

Jackson 支持 Java 8 日期类型,但需要以下依赖:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>$jackson.version</version>
</dependency>

然后配置你的ObjectMapper:

@Configuration
public class JacksonConfig 

    @Bean
    public ObjectMapper createObjectMapper()   
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    

默认情况下,日期将以ISO 8601 格式序列化。如果要更改格式,可以使用@JsonFormat

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM")
private LocalDate date;

不需要自定义(反)序列化程序。

【讨论】:

【参考方案3】:

您可以自定义 LocalDate 反序列化器。当 LocalDate 变量的 setter 方法被调用时,这个 Deserializer 将被调用。

步骤如下:

    定义一个自定义反序列化器

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate>
    
      @Override
      public LocalDate deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException 
    
          DateTimeFormatter formatter = DateTimeFormatter.ofPattern("required format");
    
          LocalDate localDate = null;
          localDate = LocalDate.parse(p.getText(), formatter);
    
          return localDate;
      
    
    

注意:LocalDate.parse 方法的参考。

    在变量上方定义@JsonDeserialize注解

    @JsonDeserialize(using=LocalDateDeserializer.class)
    private LocalDate quantityAtDate;
    

对于使用@JsonDeserialize 注解导入如下:

导入 com.fasterxml.jackson.databind.annotation.JsonDeserialize;

希望这会有所帮助。

【讨论】:

我认为我们必须像这里提到的 baeldung.com/jackson-deserialization 那样使用 objectmapper 注册这个反序列化器。 @Barath 实际上在我的 Spring MVC 应用程序中我没有注册它。可能是 Spring 在找到注释时自动执行此操作。【参考方案4】:

利用@JsonFormat定义日期格式

http://www.baeldung.com/jackson-serialize-dates

public class InventorySystemModel 

     private String materialId;
        private String materialName;
        private String materialCategory;
        private String currency;
        private double unitCostInCurrency;
        private int quantityLevel;
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        private Date quantityAtDate;
        private String warehouseName;
//getters and setters

请求:


    "materialId": "ID02",
    "materialName": "Material_2",
    "materialCategory": "LIQUID",
    "currency": "RUPEES",
    "unitCostInCurrency": 2200.0,
    "quantityLevel": 1000,
    "quantityAtDate": "2016-04-11",
    "warehouseName": "WareHouse_2"

回应:

InventorySystemModel [materialId=ID02, materialName=Material_2, materialCategory=LIQUID, currency=RUPEES, unitCostInCurrency=2200.0, quantityLevel=1000, quantityAtDate=Mon Apr 11 05:30:00 IST 2016, warehouseName=WareHouse_2]

【讨论】:

以上是关于如何以 POST 方法在 REST API 中发送日期的主要内容,如果未能解决你的问题,请参考以下文章

将雪花数据发送到 REST API (POST) 的方法

如何在swift 3中制作一个json对象以通过POST api发送它?

如何使用 REST API 中的 PUT 方法上传文件?

如何仅在 Django Rest Framework 中的另一个 API 方法中调用 API POST 方法

在Angular 4中将数据发送到JAVA post REST API时出现CORS错误

如何使用 Postman 表单数据将 POST 请求发送到 Spring Boot REST API?