- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Jackson反序列化@JsonFormat 不生效的解决方案由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
今天在线上发现一个问题,在使用Jackson进行时间的反序列化时,配置的 @JsonFormat 没有生效 。
查看源码发现,Jackson在反序列化时间时,会判断json字段值类型,如下:
由于在我们服务里,前端传时间值到后端时采用了时间戳的方式,json值被判断为数字类型,所以Jackson在反序列化时直接简单粗暴的方式处理,将时间戳转换为Date类型:
为了能够按照正确的格式解析时间,抹去后面的时间点,精确到日,只好自定义一个时间解析器。自定义的时间解析器很好实现,网上已经有很多实例代码,只需要继承 JsonDeserializer<T> 就可以.
问题的关键点在于,如何获取到注解上的时间格式,按照注解上的格式去解析,否则每个解析器的实现只能使用一种固定的格式去解析时间.
想要获取字段对应的注解信息,只有找到相应的字段,然后通过字段属性获取注解信息,再通过注解信息获取配置的格式.
但找了很久,也没有在既有的参数里找到获取相关字段的方法,只能去翻看源码,最后在这里发现了获取字段信息的方法以及解析器的生成过程,源代码如下:
第一个红框表示解析器是在这里生成的,第二个红框就是获取注解信息的地方 。
猜想,我们可不可以也实现这个类,重写生成解析器的方法?那就试试呗~ 我们在自定义的时间解析器上同样实现这个类,重写了生成时间解析器的方法,并初始化一些自定义的信息供解析时间使用(当然猜想是正确的,因为官方就是这么搞的,只是官方的是一个内部类实现的),具体代码如下:
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注解(),返回 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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我只是想知道要安装哪个版本的 Visual Studio 2010(专业版或高级版)提示升级项目.. 项目包括:asp.net mvc、数据库和silverlight。 最佳答案 通常,由不同版本的相
目录 前言 四、技术选型 五、后端接口设计 5.1业务系统接口 5.2App 端接口 六、关键逻辑实现 6.1Red
目录 前言 一、需求分析 1.1发送通知 1.2撤回通知 1.3通知消息数 1.4通知消息列表 二、数据模型设计
目录 前言 一、多租户的概念 二、隔离模式 2.1独立数据库模式 2.2共享数据库独立数据架构 2.3共享数据库共享数据架构
导读: 虽然锁在一定程度上能够解决并发问题,但稍有不慎,就可能造成死锁。本文介绍死锁的产生及处理。 死锁的产生和预防 发生死锁的必要条件有4个,分别为互斥条件、不可剥夺条件、请求与保持条件和循环等待条
在浏览网页后,我找不到任何功能来执行此操作,我有可行的个人解决方案。也许它对某人有用。 **使用 Moment 插件转换日期。***moment(currentPersianDate).clone()
是否有一种解决方案可以很好地处理数字(1-10)手写?我试过tesseract,但我得到的只是垃圾。 理想情况下是 OSS,但商业也可以。 最佳答案 OpenCV 现在带有手写数字识别 OCR 示例。
在服务器应用程序上,我们有以下内容:一个称为 JobManager 的单例类。另一个类,Scheduler,不断检查是否需要向 JobManager 添加任何类型的作业。 当需要这样做时,调度程序会执
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 5年前关闭。 Improve this qu
当您尝试从 GitHub 存储库安装某些 R 包时 install_github('rWBclimate', 'ropensci') 如果您遇到以下错误: Installing github repo
问题在以下链接中进行了描述和演示: Paul Stovell WPF: Blurry Text Rendering www.gamedev.net forum Microsoft Connect: W
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
已编辑解决方案(如下...) 我有一个启动画面,它被打包到它自己的 jar 中。它有效。 我可以通过以下方式从另一个 java 应用程序内部调用 Splash.jar: Desktop.getDesk
什么是创建像 PageFlakes 或 iGoogle 这样的门户网站的好框架/包? ?我们希望创建一个为员工提供 HR 服务的员工/HR 门户,但我们也需要一种足够灵活的产品,以便我们可以使用它来为
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
如何将 solr 与 heritrix 集成? 我想使用 heritrix 归档一个站点,然后使用 solr 在本地索引和搜索该文件。 谢谢 最佳答案 使用 Solr 进行索引的问题在于它是一个纯文本
完整日历不包含工作时间功能选项(在任何一天的议程 View 中选择第一行和最后一行 - 例如公司不工作)。我做到了类似的事情: viewDisplay: function(view){
我正在使用 bootstrap 作为我的下拉菜单。但有一个问题, 如果我的下拉菜单有多级子菜单,那么它会显示在屏幕上,并出现底部滚动条。 如何将子菜单保留在屏幕内,我需要一个 jQuery 解决方案。
我有一个要转换为 C# 的 Excel 宏。目前我手动转到一个已经打开的 IE 窗口,从中复制所有内容(html 表)并粘贴到 excel 工作表中。然后我正在处理数据。这非常有效,因为将 html
我用谷歌搜索了一下,但没有找到好的结果。 现在我正在构建一个网站,我试图从一开始就从设计的角度使其尽可能正确。 我现在面临的问题是,在决定开始使用日志记录时,我需要一个项目来放置这段代码。由于我无法在
我是一名优秀的程序员,十分优秀!