diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 46833bc17..440f846db 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -33,6 +33,7 @@ 1.78.1 0.12.6 + 2.17.2 @@ -52,9 +53,15 @@ com.fasterxml.jackson.core jackson-databind - 2.17.2 + ${jackson.version} true + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + test + com.google.code.gson gson diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/FastJSON2Engine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/FastJSON2Engine.java index ab715ea4e..30b87a19c 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/FastJSON2Engine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/FastJSON2Engine.java @@ -94,6 +94,8 @@ public class FastJSON2Engine extends AbstractJSONEngine { protected void initEngine() { if(null == this.readerContext){ this.readerContext = JSONFactory.createReadContext(); + final String dateFormat = ObjUtil.defaultIfNull(this.config, JSONEngineConfig::getDateFormat, "millis"); + this.readerContext.setDateFormat(ObjUtil.defaultIfNull(dateFormat, "millis")); } if(null == this.writerContext){ final List features = ListUtil.of(); @@ -101,6 +103,10 @@ public class FastJSON2Engine extends AbstractJSONEngine { features.add(JSONWriter.Feature.PrettyFormat); } this.writerContext = JSONFactory.createWriteContext(features.toArray(new JSONWriter.Feature[0])); + + // 自定义配置 + final String dateFormat = ObjUtil.defaultIfNull(this.config, JSONEngineConfig::getDateFormat, "millis"); + this.writerContext.setDateFormat(ObjUtil.defaultIfNull(dateFormat, "millis")); } } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/GsonEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/GsonEngine.java index 0f1d0276a..49fdc42a7 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/GsonEngine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/GsonEngine.java @@ -16,15 +16,18 @@ package org.dromara.hutool.json.engine; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.gson.*; +import org.dromara.hutool.core.date.TimeUtil; import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.json.JSONException; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.util.Date; /** * Gson引擎实现 @@ -32,7 +35,7 @@ import java.lang.reflect.Type; * @author Looly * @since 6.0.0 */ -public class GsonEngine extends AbstractJSONEngine{ +public class GsonEngine extends AbstractJSONEngine { private Gson gson; @@ -55,10 +58,10 @@ public class GsonEngine extends AbstractJSONEngine{ @Override public T deserialize(final Reader reader, final Object type) { initEngine(); - if(type instanceof Class){ - return gson.fromJson(reader, (Class)type); - } else if(type instanceof Type){ - return gson.fromJson(reader, (Type)type); + if (type instanceof Class) { + return gson.fromJson(reader, (Class) type); + } else if (type instanceof Type) { + return gson.fromJson(reader, (Type) type); } throw new JSONException("Unsupported type: {}", type.getClass()); @@ -71,14 +74,28 @@ public class GsonEngine extends AbstractJSONEngine{ @Override protected void initEngine() { - if(null != this.gson){ + if (null != this.gson) { return; } final GsonBuilder builder = new GsonBuilder(); - if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){ + if (ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)) { builder.setPrettyPrinting(); } + final String dateFormat = ObjUtil.apply(this.config, JSONEngineConfig::getDateFormat); + if (StrUtil.isNotEmpty(dateFormat)) { + builder.setDateFormat(dateFormat); + builder.registerTypeAdapter(LocalDateTime.class, (JsonDeserializer) (json, typeOfT, context) -> TimeUtil.parse(json.getAsString(), dateFormat)); + builder.registerTypeAdapter(LocalDateTime.class, (JsonSerializer) (date, type, jsonSerializationContext) -> new JsonPrimitive(TimeUtil.format(date, dateFormat))); + } else { + // 无自定义格式,则默认输出时间戳 + // https://stackoverflow.com/questions/41979086/how-to-serialize-date-to-long-using-gson + builder.registerTypeAdapter(Date.class, (JsonDeserializer) (json, typeOfT, context) -> new Date(json.getAsJsonPrimitive().getAsLong())); + builder.registerTypeAdapter(Date.class, (JsonSerializer) (date, type, jsonSerializationContext) -> new JsonPrimitive(date.getTime())); + builder.registerTypeAdapter(LocalDateTime.class, (JsonDeserializer) (json, typeOfT, context) -> TimeUtil.of(json.getAsJsonPrimitive().getAsLong())); + builder.registerTypeAdapter(LocalDateTime.class, (JsonSerializer) (date, type, jsonSerializationContext) -> new JsonPrimitive(TimeUtil.toEpochMilli(date))); + } + this.gson = builder.create(); } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/HutoolJSONEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/HutoolJSONEngine.java index 4f30b301c..632efe43c 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/HutoolJSONEngine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/HutoolJSONEngine.java @@ -60,6 +60,12 @@ public class HutoolJSONEngine extends AbstractJSONEngine { return; } - hutoolSJONConfig = JSONConfig.of(); + // 自定义配置 + final JSONConfig hutoolSJONConfig = JSONConfig.of(); + if(null != this.config){ + hutoolSJONConfig.setDateFormat(this.config.getDateFormat()); + } + + this.hutoolSJONConfig = hutoolSJONConfig; } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineConfig.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineConfig.java index f3639138e..f83990786 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineConfig.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineConfig.java @@ -40,6 +40,10 @@ public class JSONEngineConfig implements Serializable { * 是否格式化输出 */ private boolean prettyPrint; + /** + * 日期格式,null表示默认的时间戳 + */ + private String dateFormat; /** * 获取是否启用格式化输出 @@ -60,4 +64,25 @@ public class JSONEngineConfig implements Serializable { this.prettyPrint = prettyPrint; return this; } + + /** + * 日期格式,null表示默认的时间戳 + * + * @return 日期格式,null表示默认的时间戳 + */ + public String getDateFormat() { + return dateFormat; + } + + /** + * 设置日期格式,null表示默认的时间戳
+ * 此方法设置的日期格式仅对转换为JSON字符串有效,对解析JSON为bean无效。 + * + * @param dateFormat 日期格式,null表示默认的时间戳 + * @return this + */ + public JSONEngineConfig setDateFormat(final String dateFormat) { + this.dateFormat = dateFormat; + return this; + } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineFactory.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineFactory.java index 4bf314962..bbf02e663 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineFactory.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JSONEngineFactory.java @@ -51,6 +51,9 @@ public class JSONEngineFactory { if(StrUtil.equalsIgnoreCase("fastjson", engineName)){ engineName = "FastJSON2"; } + if(StrUtil.equalsIgnoreCase("hutool", engineName)){ + engineName = "HutoolJSON"; + } if (!StrUtil.endWithIgnoreCase(engineName, "Engine")) { engineName = engineName + "Engine"; diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JacksonEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JacksonEngine.java index ec45794d6..7b94f96ff 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/JacksonEngine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/JacksonEngine.java @@ -19,10 +19,14 @@ package org.dromara.hutool.json.engine; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.reflect.ConstructorUtil; +import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.json.JSONException; @@ -48,6 +52,16 @@ public class JacksonEngine extends AbstractJSONEngine { Assert.notNull(ObjectMapper.class); } + /** + * 获取Jackson的{@link ObjectMapper}对象 + * + * @return {@link ObjectMapper}对象 + */ + public ObjectMapper getMapper() { + initEngine(); + return mapper; + } + @Override public void serialize(final Object bean, final Writer writer) { initEngine(); @@ -84,7 +98,7 @@ public class JacksonEngine extends AbstractJSONEngine { @Override protected void initEngine() { - if(null != this.mapper){ + if (null != this.mapper) { return; } @@ -99,10 +113,36 @@ public class JacksonEngine extends AbstractJSONEngine { ); // 自定义配置 - if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){ + if (ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)) { mapper.enable(SerializationFeature.INDENT_OUTPUT); } + final String dateFormat = ObjUtil.apply(this.config, JSONEngineConfig::getDateFormat); + if(StrUtil.isNotEmpty(dateFormat)){ + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.setDateFormat(DateUtil.newSimpleFormat(dateFormat)); + } + + // 支持Java8+日期格式 + registerModule(mapper, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"); this.mapper = mapper; } + + /** + * 注册模块 + * + * @param mapper Jackson的{@link ObjectMapper}对象 + * @param moduleClass 模块类名 + */ + @SuppressWarnings("SameParameterValue") + private void registerModule(final ObjectMapper mapper, final String moduleClass) { + final Class aClass; + try { + aClass = Class.forName(moduleClass); + } catch (final ClassNotFoundException ignore) { + //用户未引入JSR310,则跳过不加载模块 + return; + } + mapper.registerModule((Module) ConstructorUtil.newInstance(aClass)); + } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/BeanWithDate.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/BeanWithDate.java new file mode 100644 index 000000000..c41ae2a56 --- /dev/null +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/BeanWithDate.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.json.engine; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Date; + +@Data +@AllArgsConstructor +public class BeanWithDate { + private Date date1; + private LocalDateTime date2; +} diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/FastJSONTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/FastJSONTest.java index fea9da10d..de02063a2 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/FastJSONTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/FastJSONTest.java @@ -1,5 +1,8 @@ package org.dromara.hutool.json.engine; +import org.dromara.hutool.core.date.DateTime; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.date.TimeUtil; import org.dromara.hutool.core.text.StrUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -20,4 +23,17 @@ public class FastJSONTest { " \"gender\":true\n" + "}", jsonString); } + + @Test + void writeDateFormatTest() { + final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); + final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); + final JSONEngine engine = JSONEngineFactory.createEngine("fastjson"); + + final String jsonString = engine.toJsonString(bean); + Assertions.assertEquals("{\"date1\":1704042741000,\"date2\":1704042741000}", jsonString); + + engine.init(JSONEngineConfig.of().setDateFormat("yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("{\"date1\":\"2024-01-01 01:12:21\",\"date2\":\"2024-01-01 01:12:21\"}", engine.toJsonString(bean)); + } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/GsonTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/GsonTest.java index 74d575498..42f4db406 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/GsonTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/GsonTest.java @@ -1,5 +1,8 @@ package org.dromara.hutool.json.engine; +import org.dromara.hutool.core.date.DateTime; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.date.TimeUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -21,4 +24,17 @@ public class GsonTest { " \"gender\": true\n" + "}", jsonString); } + + @Test + void writeDateFormatTest() { + final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); + final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); + final JSONEngine engine = JSONEngineFactory.createEngine("gson"); + + final String jsonString = engine.toJsonString(bean); + Assertions.assertEquals("{\"date1\":1704042741000,\"date2\":1704042741000}", jsonString); + + engine.init(JSONEngineConfig.of().setDateFormat("yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("{\"date1\":\"2024-01-01 01:12:21\",\"date2\":\"2024-01-01 01:12:21\"}", engine.toJsonString(bean)); + } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/HutoolJSONTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/HutoolJSONTest.java index be50e5668..ab13bb9b3 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/HutoolJSONTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/HutoolJSONTest.java @@ -1,5 +1,8 @@ package org.dromara.hutool.json.engine; +import org.dromara.hutool.core.date.DateTime; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.date.TimeUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -17,4 +20,17 @@ public class HutoolJSONTest { " \"gender\": true\n" + "}", jsonString); } + + @Test + void writeDateFormatTest() { + final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); + final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); + final JSONEngine engine = JSONEngineFactory.createEngine("hutool"); + + final String jsonString = engine.toJsonString(bean); + Assertions.assertEquals("{\"date1\":1704042741000,\"date2\":1704042741000}", jsonString); + + engine.init(JSONEngineConfig.of().setDateFormat("yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("{\"date1\":\"2024-01-01 01:12:21\",\"date2\":\"2024-01-01 01:12:21\"}", engine.toJsonString(bean)); + } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JacksonTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JacksonTest.java index ecab696f4..bfe327ecc 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JacksonTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JacksonTest.java @@ -1,5 +1,8 @@ package org.dromara.hutool.json.engine; +import org.dromara.hutool.core.date.DateTime; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.date.TimeUtil; import org.dromara.hutool.core.text.StrUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -23,4 +26,18 @@ public class JacksonTest { " \"gender\" : true\n" + "}", jsonString); } + + @Test + void writeDateFormatTest() { + final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); + final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); + final JSONEngine engine = JSONEngineFactory.createEngine("jackson"); + + final String jsonString = engine.toJsonString(bean); + Assertions.assertEquals("{\"date1\":1704042741000,\"date2\":[2024,1,1,1,12,21]}", jsonString); + + //TODO LocalDateTime的格式化未解决 + engine.init(JSONEngineConfig.of().setDateFormat("yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("{\"date1\":\"2024-01-01 01:12:21\",\"date2\":\"2024-01-01T01:12:21\"}", engine.toJsonString(bean)); + } }