Jackson反序列化@JsonFormat|Jackson反序列化@JsonFormat 不生效的解决方案
今天在线上发现一个问题,在使用Jackson进行时间的反序列化时,配置的 @JsonFormat 没有生效
查看源码发现,Jackson在反序列化时间时,会判断json字段值类型,如下:
文章图片
由于在我们服务里,前端传时间值到后端时采用了时间戳的方式,json值被判断为数字类型,所以Jackson在反序列化时直接简单粗暴的方式处理,将时间戳转换为Date类型:
文章图片
为了能够按照正确的格式解析时间,抹去后面的时间点,精确到日,只好自定义一个时间解析器。自定义的时间解析器很好实现,网上已经有很多实例代码,只需要继承 JsonDeserializer
问题的关键点在于,如何获取到注解上的时间格式,按照注解上的格式去解析,否则每个解析器的实现只能使用一种固定的格式去解析时间。
1. 所以第一步是获取注解上配置的信息
想要获取字段对应的注解信息,只有找到相应的字段,然后通过字段属性获取注解信息,再通过注解信息获取配置的格式。
但找了很久,也没有在既有的参数里找到获取相关字段的方法,只能去翻看源码,最后在这里发现了获取字段信息的方法以及解析器的生成过程,源代码如下:
文章图片
第一个红框表示解析器是在这里生成的,第二个红框就是获取注解信息的地方
2. 注解获取以后便创建自定义的时间解析器
猜想,我们可不可以也实现这个类,重写生成解析器的方法?那就试试呗~ 我们在自定义的时间解析器上同样实现这个类,重写了生成时间解析器的方法,并初始化一些自定义的信息供解析时间使用(当然猜想是正确的,因为官方就是这么搞的,只是官方的是一个内部类实现的),具体代码如下:
时间解析器代码:
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.google.common.collect.Lists;
import com.tujia.rba.framework.core.remote.api.BizErrorCode;
import com.tujia.rba.framework.core.remote.api.BizException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** * @author 无名小生 Date: 2019-02-19 Time: 19:00 * @version $Id$ */public class DateJsonDeserializer extends JsonDeserializer
至此,自定义时间解析器就完成了
但是,为了能够更灵活的控制时间的解析(例如:输入的时间格式和目标时间格式不同),我又重新自定义了一个时间解析的注解,基本仿照官方的 @Format 注解,具体代码如下
自定义时间解析注解:
import com.fasterxml.jackson.annotation.JacksonAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Locale;
import java.util.TimeZone;
/** * @author 无名小生 Date: 2019-02-21 Time: 11:03 * @version $Id$ */@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationpublic @interface DeserializeFormat {/*** Value that indicates that default {@link java.util.Locale}* (from deserialization or serialization context) should be used:* annotation does not define value to use.*/public final static String DEFAULT_LOCALE = "##default";
/*** Value that indicates that default {@link java.util.TimeZone}* (from deserialization or serialization context) should be used:* annotation does not define value to use.*/public final static String DEFAULT_TIMEZONE = "##default";
/*** 按照特定的时间格式解析*/public String pattern() default "";
/*** 目标格式* @return*/public String format() default "";
/*** Structure to use for serialization: definition of mapping depends on datatype,* but usually has straight-forward counterpart in data format (JSON).* Note that commonly only a subset of shapes is available;
and if 'invalid' value* is chosen, defaults are usually used.*/public DeserializeFormat.Shape shape() default DeserializeFormat.Shape.ANY;
/*** {@link java.util.Locale} to use for serialization (if needed).* Special value of {@link #DEFAULT_LOCALE}* can be used to mean "just use the default", where default is specified* by the serialization context, which in turn defaults to system* defaults ({@link java.util.Locale#getDefault()}) unless explicitly* set to another locale.*/public String locale() default DEFAULT_LOCALE;
/*** {@link java.util.TimeZone} to use for serialization (if needed).* Special value of {@link #DEFAULT_TIMEZONE}* can be used to mean "just use the default", where default is specified* by the serialization context, which in turn defaults to system* defaults ({@link java.util.TimeZone#getDefault()}) unless explicitly* set to another locale.*/public String timezone() default DEFAULT_TIMEZONE;
/*/**********************************************************/* Value enumeration(s), value class(es)/***********************************************************//*** Value enumeration used for indicating preferred Shape;
translates* loosely to JSON types, with some extra values to indicate less precise* choices (i.e. allowing one of multiple actual shapes)*/public enum Shape{/*** Marker enum value that indicates "default" (or "whatever") choice;
needed* since Annotations can not have null values for enums.*/ANY,/*** Value that indicates shape should not be structural (that is, not* {@link #ARRAY} or {@link #OBJECT}, but can be any other shape.*/SCALAR,/*** Value that indicates that (JSON) Array type should be used.*/ARRAY,/*** Value that indicates that (JSON) Object type should be used.*/OBJECT,/*** Value that indicates that a numeric (JSON) type should be used* (but does not specify whether integer or floating-point representation* should be used)*/NUMBER,/*** Value that indicates that floating-point numeric type should be used*/NUMBER_FLOAT,/*** Value that indicates that integer number type should be used* (and not {@link #NUMBER_FLOAT}).*/NUMBER_INT,/*** Value that indicates that (JSON) String type should be used.*/STRING,/*** Value that indicates that (JSON) boolean type* (true, false) should be used.*/BOOLEAN;
public boolean isNumeric() {return (this == NUMBER) || (this == NUMBER_INT) || (this == NUMBER_FLOAT);
}public boolean isStructured() {return (this == OBJECT) || (this == ARRAY);
}}/*** Helper class used to contain information from a single {@link DeserializeFormat}* annotation.*/public static class Value{private final String pattern;
private final String format;
private final DeserializeFormat.Shape shape;
private final Locale locale;
private final String timezoneStr;
// lazily constructed when created from annotationsprivate TimeZone _timezone;
public Value() {this("", "", DeserializeFormat.Shape.ANY, "", "");
}public Value(DeserializeFormat ann) {this(ann.pattern(),ann.format(), ann.shape(), ann.locale(), ann.timezone());
}public Value(String p, String f, DeserializeFormat.Shape sh, String localeStr, String tzStr){this(p,f, sh,(localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?null : new Locale(localeStr),(tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?null : tzStr,null);
}/*** @since 2.1*/public Value(String p, String f, DeserializeFormat.Shape sh, Locale l, TimeZone tz){pattern = p;
format = f;
shape = (sh == null) ? DeserializeFormat.Shape.ANY : sh;
locale = l;
_timezone = tz;
timezoneStr = null;
}/*** @since 2.4*/public Value(String p, String f, DeserializeFormat.Shape sh, Locale l, String tzStr, TimeZone tz){pattern = p;
format = f;
shape = (sh == null) ? DeserializeFormat.Shape.ANY : sh;
locale = l;
_timezone = tz;
timezoneStr = tzStr;
}/*** @since 2.1*/public DeserializeFormat.Value withPattern(String p,String f) {return new DeserializeFormat.Value(p, f, shape, locale, timezoneStr, _timezone);
}/*** @since 2.1*/public DeserializeFormat.Value withShape(DeserializeFormat.Shape s) {return new DeserializeFormat.Value(pattern, format, s, locale, timezoneStr, _timezone);
}/*** @since 2.1*/public DeserializeFormat.Value withLocale(Locale l) {return new DeserializeFormat.Value(pattern, format, shape, l, timezoneStr, _timezone);
}/*** @since 2.1*/public DeserializeFormat.Value withTimeZone(TimeZone tz) {return new DeserializeFormat.Value(pattern, format, shape, locale, null, tz);
}public String getPattern() { return pattern;
}public String getFormat() { return format;
}public DeserializeFormat.Shape getShape() { return shape;
}public Locale getLocale() { return locale;
}/*** Alternate access (compared to {@link #getTimeZone()}) which is useful* when caller just wants time zone id to convert, but not as JDK* provided {@link TimeZone}** @since 2.4*/public String timeZoneAsString() {if (_timezone != null) {return _timezone.getID();
}return timezoneStr;
}public TimeZone getTimeZone() {TimeZone tz = _timezone;
if (tz == null) {if (timezoneStr == null) {return null;
}tz = TimeZone.getTimeZone(timezoneStr);
_timezone = tz;
}return tz;
}/*** @since 2.4*/public boolean hasShape() { return shape != DeserializeFormat.Shape.ANY;
}/*** @since 2.4*/public boolean hasPattern() {return (pattern != null) && (pattern.length() > 0);
}/*** @since 2.4*/public boolean hasFormat() {return (format != null) && (format.length() > 0);
}/*** @since 2.4*/public boolean hasLocale() { return locale != null;
}/*** @since 2.4*/public boolean hasTimeZone() {return (_timezone != null) || (timezoneStr != null && !timezoneStr.isEmpty());
}}}
使用自定义解析注解的时间解析器
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.google.common.collect.Lists;
import com.tujia.rba.framework.core.remote.api.BizErrorCode;
import com.tujia.rba.framework.core.remote.api.BizException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** * @author 无名小生 Date: 2019-02-19 Time: 19:00 * @version $Id$ */public class DateJsonDeserializer extends JsonDeserializer
@JsonFormat的使用
实体类字段中添加@JsonFormat注解(),返回 yyyy-MM-ddHH:mm:ss时间格式
@JsonFormat(pattern = "yyyy-MM-ddHH:mm:ss", timezone = "GMT+8")private Date joinedDate;
pattern
:日期格式
timezone
:时区
【Jackson反序列化@JsonFormat|Jackson反序列化@JsonFormat 不生效的解决方案】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- 2018-02-06第三天|2018-02-06第三天 不能再了,反思到位就差改变
- 吃了早餐,反而容易饿(为什么?)
- 父母越不讲道理,孩子反而越优秀!说的是你吗()
- 改变自己,先从自我反思开始
- leetcode|leetcode 92. 反转链表 II
- 那些反串过的艺人-最是美色如醉人
- 反脆弱性(工作越稳定,人生越脆弱!)
- 五一反思
- 孩子,你幼儿园毕业前,最好的礼物是来自老师们爱你的“反馈”!
- 青春逼死人