直接上代码
java
import java.io.IOException;
import java.text.ParseException;
import java.text.ParsePosition;
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
/**
* 自定义JACKSON序列化
*/
public class JsonRefUtil {
public static final DateTimeFormatter DATE_TIME_FORMATTER_WITHOUT_T = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter()
.withZone(ZoneId.systemDefault());
public static DateTimeFormatter DATE_TIME_FORMAT_WITHOUT_T_NANO = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.toFormatter()
.withZone(ZoneId.systemDefault());
/**
* 自定义 LocalDateTime 序列化器
*/
public static class MyLocalDateTimeSerializer extends LocalDateTimeSerializer {
public static final MyLocalDateTimeSerializer INSTANCE = new MyLocalDateTimeSerializer();
public MyLocalDateTimeSerializer() {
super(LocalDateTimeSerializer.INSTANCE, false, false, DATE_TIME_FORMAT_WITHOUT_T_NANO);
}
}
/**
* 自定义 LocalDateTime 反序列化器,兼容带T和不带T的日期格式
*/
public static class MyLocalDateTimeDeserializer extends LocalDateTimeDeserializer {
public static final MyLocalDateTimeDeserializer INSTANCE = new MyLocalDateTimeDeserializer();
public MyLocalDateTimeDeserializer() {
super(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
@Override
protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt,
String string0) throws IOException {
String string = string0.trim();
if (string.isEmpty()) {
return _fromEmptyString(p, ctxt, string);
}
try {
if (string.contains("T")) {
return LocalDateTime.parse(string, _formatter);
} else {
return LocalDateTime.parse(string, DATE_TIME_FORMATTER_WITHOUT_T);
}
} catch (DateTimeException e) {
return _handleDateTimeException(ctxt, e, string);
}
}
}
/**
* 自定义 Date 反序列化器,兼容带T和不带T的日期格式
*/
public static class MyStdDateFormat extends StdDateFormat {
public static final MyStdDateFormat INSTANCE = new MyStdDateFormat();
protected final static Pattern PATTERN;
static {
Pattern p = null;
try {
// 替换 DATE_FORMAT_STR_ISO8601 兼容空格分隔日起时间
p = Pattern.compile(PATTERN_PLAIN_STR
+ "[T ]\\d\\d[:]\\d\\d(?:[:]\\d\\d)?" // hours, minutes, optional seconds
+ "(\\.\\d+)?" // optional second fractions
+ "(Z|[+-]\\d\\d(?:[:]?\\d\\d)?)?" // optional timeoffset/Z
);
} catch (Exception t) {
throw new RuntimeException(t);
}
PATTERN = p;
}
public MyStdDateFormat() {
super(TimeZone.getDefault(), Locale.CHINA, true);
}
@Override
public MyStdDateFormat clone() {
return new MyStdDateFormat();
}
@Override
protected Date _parseAsISO8601(String dateStr, ParsePosition bogus)
throws IllegalArgumentException, ParseException {
dateStr = dateStr.trim();
final int totalLen = dateStr.length();
// actually, one short-cut: if we end with "Z", must be UTC
TimeZone tz = TimeZone.getDefault();
if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen - 1))) {
tz = _timezone;
}
Calendar cal = _getCalendar(tz);
cal.clear();
String formatStr;
if (totalLen <= 10) {
Matcher m = PATTERN_PLAIN.matcher(dateStr);
if (m.matches()) {
int year = _parse4D(dateStr, 0);
int month = _parse2D(dateStr, 5) - 1;
int day = _parse2D(dateStr, 8);
cal.set(year, month, day, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
formatStr = DATE_FORMAT_STR_PLAIN;
} else {
if (dateStr.contains(" ")) {
}
Matcher m = PATTERN.matcher(dateStr);
if (m.matches()) {
// Important! START with optional time zone; otherwise Calendar will explode
int start = m.start(2);
int end = m.end(2);
int len = end - start;
if (len > 1) { // 0 -> none, 1 -> 'Z'
// NOTE: first char is sign; then 2 digits, then optional colon, optional 2 digits
int offsetSecs = _parse2D(dateStr, start + 1) * 3600; // hours
if (len >= 5) {
offsetSecs += _parse2D(dateStr, end - 2) * 60; // minutes
}
if (dateStr.charAt(start) == '-') {
offsetSecs *= -1000;
} else {
offsetSecs *= 1000;
}
cal.set(Calendar.ZONE_OFFSET, offsetSecs);
// 23-Jun-2017, tatu: Not sure why, but this appears to be needed too:
cal.set(Calendar.DST_OFFSET, 0);
}
int year = _parse4D(dateStr, 0);
int month = _parse2D(dateStr, 5) - 1;
int day = _parse2D(dateStr, 8);
// So: 10 chars for date, then `T`, so starts at 11
int hour = _parse2D(dateStr, 11);
int minute = _parse2D(dateStr, 14);
// Seconds are actually optional... so
int seconds;
if ((totalLen > 16) && dateStr.charAt(16) == ':') {
seconds = _parse2D(dateStr, 17);
} else {
seconds = 0;
}
cal.set(year, month, day, hour, minute, seconds);
// Optional milliseconds
start = m.start(1) + 1;
end = m.end(1);
int msecs = 0;
if (start >= end) { // no fractional
cal.set(Calendar.MILLISECOND, 0);
} else {
// first char is '.', but rest....
msecs = 0;
final int fractLen = end - start;
switch (fractLen) {
default: // [databind#1745] Allow longer fractions... for now, cap at nanoseconds tho
if (fractLen > 9) { // only allow up to nanos
throw new ParseException(String.format(
"Cannot parse date \"%s\": invalid fractional seconds '%s'; can use at most 9 digits",
dateStr, m.group(1).substring(1)
), start);
}
// fall through
case 3:
msecs += (dateStr.charAt(start + 2) - '0');
case 2:
msecs += 10 * (dateStr.charAt(start + 1) - '0');
case 1:
msecs += 100 * (dateStr.charAt(start) - '0');
break;
case 0:
break;
}
cal.set(Calendar.MILLISECOND, msecs);
}
return cal.getTime();
}
formatStr = DATE_FORMAT_STR_ISO8601;
}
throw new ParseException
(String.format("Cannot parse date \"%s\": while it seems to fit format '%s', parsing fails (leniency? %s)",
dateStr, formatStr, _lenient),
// [databind#1742]: Might be able to give actual location, some day, but for now
// we can't give anything more indicative
0);
}
private static int _parse4D(String str, int index) {
return (1000 * (str.charAt(index) - '0'))
+ (100 * (str.charAt(index + 1) - '0'))
+ (10 * (str.charAt(index + 2) - '0'))
+ (str.charAt(index + 3) - '0');
}
private static int _parse2D(String str, int index) {
return (10 * (str.charAt(index) - '0'))
+ (str.charAt(index + 1) - '0');
}
@Override
protected void _format(TimeZone tz, Locale loc, Date date,
StringBuffer buffer) {
Calendar cal = _getCalendar(tz);
cal.setTime(date);
// [databind#2167]: handle range beyond [1, 9999]
final int year = cal.get(Calendar.YEAR);
// Assuming GregorianCalendar, special handling needed for BCE (aka BC)
if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
_formatBCEYear(buffer, year);
} else {
if (year > 9999) {
buffer.append('+');
}
pad4(buffer, year);
}
buffer.append('-');
pad2(buffer, cal.get(Calendar.MONTH) + 1);
buffer.append('-');
pad2(buffer, cal.get(Calendar.DAY_OF_MONTH));
buffer.append(' ');
pad2(buffer, cal.get(Calendar.HOUR_OF_DAY));
buffer.append(':');
pad2(buffer, cal.get(Calendar.MINUTE));
buffer.append(':');
pad2(buffer, cal.get(Calendar.SECOND));
buffer.append('.');
pad3(buffer, cal.get(Calendar.MILLISECOND));
}
private static void pad2(StringBuffer buffer, int value) {
int tens = value / 10;
if (tens == 0) {
buffer.append('0');
} else {
buffer.append((char) ('0' + tens));
value -= 10 * tens;
}
buffer.append((char) ('0' + value));
}
private static void pad3(StringBuffer buffer, int value) {
int h = value / 100;
if (h == 0) {
buffer.append('0');
} else {
buffer.append((char) ('0' + h));
value -= (h * 100);
}
pad2(buffer, value);
}
private static void pad4(StringBuffer buffer, int value) {
int h = value / 100;
if (h == 0) {
buffer.append('0').append('0');
} else {
if (h > 99) { // [databind#2167]: handle above 9999 correctly
buffer.append(h);
} else {
pad2(buffer, h);
}
value -= (100 * h);
}
pad2(buffer, value);
}
}
public static final DateDeserializer MY_DATE_DESERIALIZER = new DateDeserializer(DateDeserializer.instance, MyStdDateFormat.INSTANCE,
null);
public static final DateSerializer MY_DATE_SERIALIZER = new DateSerializer(false, MyStdDateFormat.INSTANCE);
/**
* 创建自定义的 {@link SimpleModule},
*/
public static SimpleModule myModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDateTime.class, MyLocalDateTimeSerializer.INSTANCE);
module.addSerializer(Date.class, MY_DATE_SERIALIZER);
module.addDeserializer(LocalDateTime.class, MyLocalDateTimeDeserializer.INSTANCE);
module.addDeserializer(Date.class, MY_DATE_DESERIALIZER);
return module;
}
/**
* 标准 json mapper
*/
public static final JsonMapper jsonMapper = JsonMapper.builder()
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES)
//.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.defaultDateFormat(MyStdDateFormat.INSTANCE)
.addModule(new JavaTimeModule())
.addModule(myModule())
.build();
}
测试它
java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.JSON;
public class XxTest {
public Date a;
public LocalDate b;
public LocalDateTime c;
public LocalTime d;
public static void main(String[] args) throws Exception{
var x= new XxTest();
x.a=new Date();
x.b=LocalDate.now();
x.c=LocalDateTime.now();
x.d=LocalTime.now();
System.out.println("直接序列");
var json = JsonRefUtil.jsonMapper.writeValueAsString(x);
System.out.println(json);
for (String s : List.of("2026-02-11","2026-02-11 12:15:29","2026-02-11 12:15:29.123"
,"2026-02-11 12:15:29Z","2026-02-11 12:15:29+08:00")) {
test(s);
}
var s = """
{"a":'2026-02-11 12:15:29',"b":"2026-02-11","c":"2026-02-11 12:15:29.1","d":"12:15:29.1",f:1}
""";
var y = BeanRefUtil.jsonToBean(s, XxTest.class,true);
System.out.println(JSON.toJSONString(y));
var s1 = """
{"a":'2026-02-11T12:15:29',"b":"2026-02-11","c":"2026-02-11 12:15:29","d":"12:15:29"}
""";
var y1 = BeanRefUtil.jsonToBean(s1, XxTest.class,true);
System.out.println(JSON.toJSONString(y1));
var s2 = """
{"a":'2026-02-11 12:15:29Z',"b":"2026-02-11","c":"2026-02-11 12:15:29","d":"12:15:29"}
""";
var y2 = BeanRefUtil.jsonToBean(s2, XxTest.class,true);
System.out.println(JSON.toJSONString(y2));
var s3 = """
{"a":1770783329000,"b":"2026-02-11","c":"2026-02-11 12:15:29","d":"12:15:29"}
""";
var y3 = BeanRefUtil.jsonToBean(s3, XxTest.class,true);
System.out.println(JSON.toJSONString(y3));
}
private static void test(String a){
var s = "{\"a\":\""+a+"\"}";
System.out.println("序列"+a);
var y = BeanRefUtil.jsonToBean(s, XxTest.class,true);
System.out.println(JSON.toJSONString(y));
System.out.println(JSON.toJSONString(JSON.parseObject(s,XxTest.class)));
}
}
控制台输出符合预期
plian
直接序列
{"a":"2026-02-13 14:38:58.732","b":"2026-02-13","c":"2026-02-13 14:38:58","d":"14:38:58.743665534"}
序列2026-02-11
{"a":1770739200000}
{"a":1770739200000}
序列2026-02-11 12:15:29
{"a":1770783329000}
{"a":1770783329000}
序列2026-02-11 12:15:29.123
{"a":1770783329123}
{"a":1770783329123}
序列2026-02-11 12:15:29Z
{"a":1770783329000}
{"a":1770812129000}
序列2026-02-11 12:15:29+08:00
{"a":1770783329000}
{"a":1770783329000}
{"a":1770783329000,"b":"2026-02-11","c":"2026-02-11T12:15:29.100","d":"12:15:29.100"}
{"a":1770783329000,"b":"2026-02-11","c":"2026-02-11T12:15:29","d":"12:15:29"}
{"a":1770783329000,"b":"2026-02-11","c":"2026-02-11T12:15:29","d":"12:15:29"}
{"a":1770783329000,"b":"2026-02-11","c":"2026-02-11T12:15:29","d":"12:15:29"}