gpt4 book ai didi

Jackson反序列化@JsonFormat 不生效的解决方案

转载 作者:qq735679552 更新时间:2022-09-27 22:32:09 28 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Jackson反序列化@JsonFormat 不生效的解决方案由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

今天在线上发现一个问题,在使用Jackson进行时间的反序列化时,配置的 @JsonFormat 没有生效 。

查看源码发现,Jackson在反序列化时间时,会判断json字段值类型,如下:

Jackson反序列化@JsonFormat 不生效的解决方案

由于在我们服务里,前端传时间值到后端时采用了时间戳的方式,json值被判断为数字类型,所以Jackson在反序列化时直接简单粗暴的方式处理,将时间戳转换为Date类型:

Jackson反序列化@JsonFormat 不生效的解决方案

为了能够按照正确的格式解析时间,抹去后面的时间点,精确到日,只好自定义一个时间解析器。自定义的时间解析器很好实现,网上已经有很多实例代码,只需要继承 JsonDeserializer<T> 就可以.

问题的关键点在于,如何获取到注解上的时间格式,按照注解上的格式去解析,否则每个解析器的实现只能使用一种固定的格式去解析时间.

1. 所以第一步是获取注解上配置的信息

想要获取字段对应的注解信息,只有找到相应的字段,然后通过字段属性获取注解信息,再通过注解信息获取配置的格式.

但找了很久,也没有在既有的参数里找到获取相关字段的方法,只能去翻看源码,最后在这里发现了获取字段信息的方法以及解析器的生成过程,源代码如下:

Jackson反序列化@JsonFormat 不生效的解决方案

第一个红框表示解析器是在这里生成的,第二个红框就是获取注解信息的地方 。

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<Date> implements ContextualDeserializer {    private final static Logger logger = LoggerFactory.getLogger(DateJsonDeserializer.class);    private final static List<String> FORMATS = Lists.newArrayList(        "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyyMMdd-HHmmss", "yyyy-MM-dd", "MM-dd", "HH:mm:ss", "yyyy-MM"    );    public final DateFormat df;    public final String formatString;    public DateJsonDeserializer() {        this.df = null;        this.formatString = null;    }    public DateJsonDeserializer(DateFormat df) {        this.df = df;        this.formatString = "";    }    public DateJsonDeserializer(DateFormat df, String formatString) {        this.df = df;        this.formatString = formatString;    }    @Override    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {        try {            String dateValue = p.getText();            if (df == null || StringUtils.isEmpty(dateValue)) {                return null;            }            logger.info("使用自定义解析器解析字段:{}:时间:{}",p.getCurrentName(),p.getText());            Date date;            if (StringUtils.isNumeric(dateValue)){                date = new Date(Long.valueOf(dateValue));            }else {                String[] patterns = FORMATS.toArray(new String[0]);                date = DateUtils.parseDate(p.getText(),patterns);            }            return df.parse(df.format(date));        } catch (ParseException | SecurityException e) {            logger.error("JSON反序列化,时间解析失败", e);            throw new BizException(BizErrorCode.UNEXPECTED_ERROR);        }    }    @Override    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {        if (property != null) {            JsonFormat.Value format = ctxt.getAnnotationIntrospector().findFormat(property.getMember());            if (format != null) {                TimeZone tz = format.getTimeZone();                // First: fully custom pattern?                if (format.hasPattern()) {                    final String pattern = format.getPattern();                    if (!FORMATS.contains(pattern)){                        FORMATS.add(pattern);                    }                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();                    SimpleDateFormat df = new SimpleDateFormat(pattern, loc);                    if (tz == null) {                        tz = ctxt.getTimeZone();                    }                    df.setTimeZone(tz);                    return new DateJsonDeserializer(df, pattern);                }                // But if not, can still override timezone                if (tz != null) {                    DateFormat df = ctxt.getConfig().getDateFormat();                    // one shortcut: with our custom format, can simplify handling a bit                    if (df.getClass() == StdDateFormat.class) {                        final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();                        StdDateFormat std = (StdDateFormat) df;                        std = std.withTimeZone(tz);                        std = std.withLocale(loc);                        df = std;                    } else {                        // otherwise need to clone, re-set timezone:                        df = (DateFormat) df.clone();                        df.setTimeZone(tz);                    }                    return new DateJsonDeserializer(df);                }            }        }        return this;    }}

至此,自定义时间解析器就完成了 。

但是,为了能够更灵活的控制时间的解析(例如:输入的时间格式和目标时间格式不同),我又重新自定义了一个时间解析的注解,基本仿照官方的 @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 annotations        private 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<Date> implements ContextualDeserializer {    private final static Logger logger = LoggerFactory.getLogger(DateJsonDeserializer.class);    private final static List<String> FORMATS = Lists        .newArrayList("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyyMMdd-HHmmss", "yyyy-MM-dd", "MM-dd", "HH:mm:ss",                      "yyyy-MM");    public final DateFormat df;    public final String formatString;    public DateJsonDeserializer() {        this.df = null;        this.formatString = null;    }    public DateJsonDeserializer(DateFormat df) {        this.df = df;        this.formatString = "";    }    public DateJsonDeserializer(DateFormat df, String formatString) {        this.df = df;        this.formatString = formatString;    }    @Override    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {        try {            String dateValue = p.getText();            if (df == null || StringUtils.isEmpty(dateValue)) {                return null;            }            Date date;            if (StringUtils.isNumeric(dateValue)){                date = new Date(Long.valueOf(dateValue));            }else {                String[] formatArray = FORMATS.toArray(new String[0]);                date = DateUtils.parseDate(p.getText(),formatArray);            }            return df.parse(df.format(date));        } catch (ParseException | SecurityException e) {            logger.error("JSON反序列化,时间解析失败", e);            throw new BizException(BizErrorCode.UNEXPECTED_ERROR);        }    }    @Override    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {        if (property != null) {//            JsonFormat.Value format = ctxt.getAnnotationIntrospector().findFormat(property.getMember());            DeserializeFormat deFormat = property.getAnnotation(DeserializeFormat.class);            DeserializeFormat.Value format = (deFormat == null)  ? null : new DeserializeFormat.Value(deFormat);            if (format != null) {                TimeZone tz = format.getTimeZone();                // First: fully custom pattern?                if (format.hasPattern() && !FORMATS.contains(format.getPattern())){                    FORMATS.add(format.getPattern());                }                if (format.hasFormat()) {                    final String dateFormat = format.getFormat();                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();                    SimpleDateFormat df = new SimpleDateFormat(dateFormat, loc);                    if (tz == null) {                        tz = ctxt.getTimeZone();                    }                    df.setTimeZone(tz);                    return new DateJsonDeserializer(df, dateFormat);                }                // But if not, can still override timezone                if (tz != null) {                    DateFormat df = ctxt.getConfig().getDateFormat();                    // one shortcut: with our custom format, can simplify handling a bit                    if (df.getClass() == StdDateFormat.class) {                        final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();                        StdDateFormat std = (StdDateFormat) df;                        std = std.withTimeZone(tz);                        std = std.withLocale(loc);                        df = std;                    } else {                        // otherwise need to clone, re-set timezone:                        df = (DateFormat) df.clone();                        df.setTimeZone(tz);                    }                    return new DateJsonDeserializer(df);                }            }        }        return this;    }}

@JsonFormat的使用

实体类字段中添加@JsonFormat注解(),返回 yyyy-MM-dd  HH:mm:ss  时间格式 。

@JsonFormat(pattern = "yyyy-MM-dd  HH:mm:ss", timezone = "GMT+8")private Date joinedDate;

pattern:日期格式 。

timezone:时区 。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我.

原文链接:https://blog.csdn.net/qq_26881739/article/details/87862506 。

最后此篇关于Jackson反序列化@JsonFormat 不生效的解决方案的文章就讲到这里了,如果你想了解更多关于Jackson反序列化@JsonFormat 不生效的解决方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com