我有一个来自 API 的日期格式,如下所示:
"start_time": "2015-10-1 3:00 PM GMT+1:00"
这是 YYYY-DD-MM HH:MM am/pm GMT 时间戳。我将此值映射到 POJO 中的日期变量。显然,它显示转换错误。
我想知道两件事:
- 我需要使用什么格式与 Jackson 进行转换?Date 是一个很好的字段类型吗?
- 一般来说,有没有办法在杰克逊将变量映射到对象成员之前对其进行处理?比如改变格式、计算等。
从 Jackson v2.0 开始,您可以直接在 Object 成员上使用 @JsonFormat 注解;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
我需要使用什么格式与 Jackson 进行转换?Date 是一个很好的字段类型吗?
Date
是一个很好的字段类型。您可以使用以下方法非常轻松地使 JSON 可解析ObjectMapper.setDateFormat
:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);
一般来说,有没有办法在杰克逊将变量映射到对象成员之前对其进行处理?比如改变格式、计算等。
是的。您有几个选择,包括实现自定义JsonDeserializer
,例如扩展JsonDeserializer<Date>
。这是一个好的开始。
当然,有一种称为序列化和反序列化的自动化方式,您也可以使用 pb2q 提到的特定注释(@JsonSerialize、@JsonDeserialize)来定义它。
您可以同时使用 java.util.Date 和 java.util.Calendar ...,也可以使用 JodaTime。
@JsonFormat 注释在反序列化期间对我不起作用(它已将时区调整为不同的值)(序列化工作完美):
@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")
@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")
如果您想要预测结果,则需要使用自定义序列化器和自定义反序列化器而不是 @JsonFormat 注释。我在这里找到了真正好的教程和解决方案http://www.baeldung.com/jackson-serialize-dates
有日期字段的示例,但我需要日历字段,所以这是我的实现:
序列化程序类:
public class CustomCalendarSerializer extends JsonSerializer<Calendar> {
public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");
@Override
public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
throws IOException, JsonProcessingException {
if (value == null) {
gen.writeNull();
} else {
gen.writeString(FORMATTER.format(value.getTime()));
}
}
}
解串器类:
public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {
@Override
public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
throws IOException, JsonProcessingException {
String dateAsString = jsonparser.getText();
try {
Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
Calendar calendar = Calendar.getInstance(
CustomCalendarSerializer.LOCAL_TIME_ZONE,
CustomCalendarSerializer.LOCALE_HUNGARIAN
);
calendar.setTime(date);
return calendar;
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
以及上述类的用法:
public class CalendarEntry {
@JsonSerialize(using = CustomCalendarSerializer.class)
@JsonDeserialize(using = CustomCalendarDeserializer.class)
private Calendar calendar;
// ... additional things ...
}
使用此实现,序列化和反序列化过程的执行连续产生原始值。
仅使用 @JsonFormat 注释,反序列化会给出不同的结果,我认为由于库内部时区默认设置,您无法使用注释参数更改(这也是我对 Jackson 库 2.5.3 和 2.6.3 版本的经验)。
在日期中添加 T 和 Z 等字符
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;
输出
{
"currentTime": "2019-12-11T11:40:49Z"
}
只是具有日期时间格式的Spring Boot应用程序的完整示例RFC3339
package bj.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import java.text.SimpleDateFormat;
/**
* Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
*/
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {
public static void main(String[] args) {
SpringApplication.run(BarApp.class, args);
}
@Autowired
private ObjectMapper objectMapper;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
}
}
基于@miklov-kriven 非常有用的答案,我希望这两个额外的考虑点对某人有所帮助:
(1) 我发现将序列化程序和反序列化程序作为静态内部类包含在同一个类中是一个好主意。注意,使用 ThreadLocal 来保证 SimpleDateFormat 的线程安全。
public class DateConverter {
private static final ThreadLocal<SimpleDateFormat> sdf =
ThreadLocal.<SimpleDateFormat>withInitial(
() -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});
public static class Serialize extends JsonSerializer<Date> {
@Override
public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
if (value == null) {
jgen.writeNull();
}
else {
jgen.writeString(sdf.get().format(value));
}
}
}
public static class Deserialize extends JsonDeserializer<Date> {
@Overrride
public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
String dateAsString = jp.getText();
try {
if (Strings.isNullOrEmpty(dateAsString)) {
return null;
}
else {
return new Date(sdf.get().parse(dateAsString).getTime());
}
}
catch (ParseException pe) {
throw new RuntimeException(pe);
}
}
}
}
(2) 作为对每个单独的类成员使用 @JsonSerialize 和 @JsonDeserialize 注释的替代方法,您还可以考虑通过在应用程序级别应用自定义序列化来覆盖 Jackson 的默认序列化,即所有 Date 类型的类成员都将由 Jackson 序列化使用此自定义序列化,而无需在每个字段上显式注释。例如,如果您使用 Spring Boot,则一种方法如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Module customModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new DateConverter.Serialize());
module.addDeserializer(Date.class, new Dateconverter.Deserialize());
return module;
}
}
为我工作。春季启动。
import com.alibaba.fastjson.annotation.JSONField;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
输出:
{
"createTime": "2019-06-14 13:07:21"
}
如果有人在为 java.sql.Date 使用自定义日期格式时遇到问题,这是最简单的解决方案:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);
(这个 SO-answer 为我省去了很多麻烦:https ://stackoverflow.com/a/35212795/3149048 )
Jackson 默认为 java.sql.Date 使用 SqlDateSerializer,但目前,此序列化程序不考虑日期格式,请参阅此问题:https ://github.com/FasterXML/jackson-databind/issues/1407 。解决方法是为 java.sql.Date 注册不同的序列化程序,如代码示例所示。
我想指出,设置SimpleDateFormat
另一个答案中描述的类似仅适用于java.util.Date
我认为问题中的意思。但是对于java.sql.Date
格式化程序不起作用。在我的情况下,为什么格式化程序不起作用并不是很明显,因为在应该序列化的模型中,该字段实际上是 ajava.utl.Date
但实际对象最终是 a java.sql.Date
。这是可能的,因为
public class java.sql extends java.util.Date
所以这实际上是有效的
java.util.Date date = new java.sql.Date(1542381115815L);
因此,如果您想知道为什么您的 Date 字段格式不正确,请确保该对象确实是java.util.Date
.
这里也提到了为什么java.sql.Date
不会添加处理。
这将是重大变化,我认为这是没有道理的。如果我们从头开始,我会同意这种改变,但事实并非如此。