outline:
1. The scene that appears.
2. Error content and code tracking.
3. Reason.
4. Three solutions.
1. Scenarios that appear:
- The server provides a springcloud interface.
- The client calls this interface through feign, the return value is a list of CompanyDTO, and there is a Date object in the DTO.
- Error calling.
2. The error is as follows:
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"]) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:228) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:213) at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95) at org.springframework.cloud.netflix.feign.support.SpringDecoder.decode(SpringDecoder.java:59) at org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:47) at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:165) ... 75 more Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"]) at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:74) at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1410) at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:926) at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:822) at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:791) at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:172) at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:259) at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:242) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:277) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225) ... 80 more
- Start with the readJavaType method of AbstractJackson2HttpMessageConverter.java:225.
- In the deserialize of DateDeserializers.java, there is a piece of core logic: this._customFormat.parse(str). Here comes the question: where does _customFormat come from?
protected Date _parseDate(JsonParser p, DeserializationContext ctxt) throws IOException {
if (this._customFormat != null) {
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_STRING) {
String str = p.getText().trim();
if (str.length() == 0) {
return (Date)this.getEmptyValue(ctxt);
}
DateFormat var5 = this._customFormat;
synchronized(this._customFormat) {
Date var10000;
try {
var10000 = this._customFormat.parse(str);
} catch (ParseException var8) {
return (Date)ctxt.handleWeirdStringValue(this.handledType(), str, "expected format \"%s\"", new Object[]{this._formatString});
}
return var10000;
}
}
}
return super._parseDate(p, ctxt);
}
- _customFormat in DateBasedDeserializer, obtained through ctxt.getConfig(), DeserializationContext is the configured context.
protected abstract static class DateBasedDeserializer<T> extends StdScalarDeserializer<T> implements ContextualDeserializer {
protected final DateFormat _customFormat;
protected final String _formatString;
protected abstract DateDeserializers.DateBasedDeserializer<T> withDateFormat(DateFormat var1, String var2);
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
if (property != null) {
Value format = this.findFormatOverrides(ctxt, property, this.handledType());
if (format != null) {
TimeZone tz = format.getTimeZone();
Local place;
if (format.hasPattern()) {
String pattern = format.getPattern();
loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
if (tz == null) {
tz = ctxt.getTimeZone();
}
df.setTimeZone (tz);
return this.withDateFormat(df, pattern);
}
if (tz != null) {
DateFormat df = ctxt.getConfig().getDateFormat();
Object df;
if (df.getClass() == StdDateFormat.class) {
loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
StdDateFormat std = (StdDateFormat)df;
std = std.withTimeZone(tz);
std = std.withLocale(loc);
df = std;
} else {
df = (DateFormat)df.clone();
((DateFormat) df) .setTimeZone (tz);
}
return this.withDateFormat((DateFormat)df, this._formatString);
}
}
}
return this;
}
}
- Let's take a look at the specific implementation of getDateFormat, which is finally obtained through BaseSettings in MapperConfig.
public final DateFormat getDateFormat() { return this._base.getDateFormat(); }
3. The specific reason is that Jason only supports the following formats:
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" "EEE, dd MMM yyyy HH:mm:ss zzz" "yyyy-MM-dd"
4. Solution:
4.1 Set the DateFormat of the client.
public class MyDateFormat extends DateFormat { private DateFormat dateFormat; private SimpleDateFormat format1 = new SimpleDateFormat("yyy-MM-dd HH:mm:ss"); public MyDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; } @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { return dateFormat.format(date, toAppendTo, fieldPosition); } @Override public Date parse(String source, ParsePosition pos) { Date date = null; try { date = format1.parse(source, pos); } catch (Exception e) { date = dateFormat.parse(source, pos); } return date; } // Mainly decorate this method @Override public Date parse(String source) throws ParseException { Date date = null; try { // first follow my rules date = format1.parse(source); } catch (Exception e) { // No, then follow the original rules date = dateFormat.parse(source); } return date; } // The reason for decorating the clone method here is because the clone method is also useful in jackson @Override public Object clone() { Object format = dateFormat.clone(); return new MyDateFormat((DateFormat) format); } }
and join it at startup.
@Configuration public class WebConfig { @Autowired private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder; @Bean public MappingJackson2HttpMessageConverter MappingJsonpHttpMessageConverter() { ObjectMapper mapper = jackson2ObjectMapperBuilder.build(); // ObjectMapper in order to ensure thread safety, the configuration class inside is an immutable object // So the internal principle of setDateFormat here is actually to create a new configuration class DateFormat dateFormat = mapper.getDateFormat(); mapper.setDateFormat(new MyDateFormat(dateFormat)); MappingJackson2HttpMessageConverter mappingJsonpHttpMessageConverter = new MappingJackson2HttpMessageConverter( mapper); return mappingJsonpHttpMessageConverter; } }4.2 Solution 2
If you can pass code, you can pass configuration. . .
#spring.mvc.date-format=yyyy-MM-dd HH:mm:ss #spring.jackson.time-zone=GMT+8 #spring.jackson.date-format=yyyy-MM-dd HH:mm:ss #spring.jackson.deserialization.accept_empty_string_as_null_object=true
4.3 Solution 3
Annotating the field's setter, this method is more troublesome.
public class DateJsonDeserializer extends JsonDeserializer<Date> { /** * @see JsonDeserializer#deserialize(JsonParser, * DeserializationContext) */ @Override public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(parser.getValueAsString()); } catch (Exception e) { e.printStackTrace (); } return null; } }
Then add annotations to the fields of the DTO:
@JsonDeserialize(using = DateJsonDeserializer.class) private Date applyRefundTime;
Reference article:
https://blog.csdn.net/qq906627950/article/details/79503801
http://wujiu.iteye.com/blog/2244537