如何以 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 中发送日期的主要内容,如果未能解决你的问题,请参考以下文章