From 4db6ac1f345f7963009975649c462047830b6c25 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 28 Sep 2024 22:53:29 +0800 Subject: [PATCH] add moshi --- .../dromara/hutool/core/reflect/TypeUtil.java | 18 ++- hutool-json/pom.xml | 53 ++++++- ...eSerDesc.java => DateGsonTypeAdapter.java} | 6 +- .../hutool/json/engine/gson/GsonEngine.java | 2 +- .../json/engine/moshi/DateMoshiAdapter.java | 85 +++++++++++ .../hutool/json/engine/moshi/MoshiEngine.java | 136 ++++++++++++++++++ .../engine/moshi/TemporalMoshiAdapter.java | 72 ++++++++++ .../engine/moshi/TimeZoneMoshiAdapter.java | 70 +++++++++ .../json/engine/moshi/package-info.java | 24 ++++ .../hutool/json/xml/JSONXMLSerializer.java | 2 +- .../org.dromara.hutool.json.engine.JSONEngine | 1 + .../hutool/json/engine/JSONEngineTest.java | 2 +- 12 files changed, 462 insertions(+), 9 deletions(-) rename hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/{DateSerDesc.java => DateGsonTypeAdapter.java} (89%) create mode 100644 hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/DateMoshiAdapter.java create mode 100644 hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/MoshiEngine.java create mode 100644 hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TemporalMoshiAdapter.java create mode 100644 hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TimeZoneMoshiAdapter.java create mode 100644 hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/package-info.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java index d63f55082..584358eca 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java @@ -312,7 +312,7 @@ public class TypeUtil { * @since 4.5.2 */ public static ParameterizedType toParameterizedType(Type type, final int interfaceIndex) { - if(type instanceof TypeReference){ + if (type instanceof TypeReference) { type = ((TypeReference) type).getType(); } @@ -473,6 +473,22 @@ public class TypeUtil { return parameterizedType; } + /** + * 创建泛型对象,例如: + * + *
{@code
+	 *     List list = TypeUtil.createParameterizedType(List.class, String.class);
+	 * }
+ * + * @param rawType 原始类型,例如:List.class + * @param actualTypeArguments 实际类型,例如:String.class + * @return 泛型对象 + * @since 6.0.0 + */ + public static Type createParameterizedType(final Type rawType, final Type... actualTypeArguments) { + return new ParameterizedTypeImpl(actualTypeArguments, null, rawType); + } + /** * 获得泛型变量对应的泛型实际类型,如果此变量没有对应的实际类型,返回null * diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 2d305599c..e0aa29f23 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -38,6 +38,10 @@ 1.78.1 0.12.6 2.17.2 + 2.11.0 + 2.0.41 + 1.15.1 + 1.37 @@ -69,13 +73,19 @@ com.google.code.gson gson - 2.11.0 + ${gson.version} true com.alibaba.fastjson2 fastjson2 - 2.0.41 + ${fastjson2.version} + true + + + com.squareup.moshi + moshi + 1.15.1 true @@ -98,6 +108,45 @@ ${jjwt.version} test + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-jmh-resource + generate-test-sources + + add-test-resource + + + + src/test/jmh + my-resources + + + + + + + + diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateSerDesc.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateGsonTypeAdapter.java similarity index 89% rename from hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateSerDesc.java rename to hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateGsonTypeAdapter.java index ce638917c..6dc238ef7 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateSerDesc.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/DateGsonTypeAdapter.java @@ -30,12 +30,12 @@ import java.util.Date; * @author Looly * @since 6.0.0 */ -public class DateSerDesc implements GsonTypeAdapter { +public class DateGsonTypeAdapter implements GsonTypeAdapter { /** * 默认日期格式化描述,默认为null,表示使用时间戳 */ - public static final DateSerDesc INSTANCE = new DateSerDesc(null); + public static final DateGsonTypeAdapter INSTANCE = new DateGsonTypeAdapter(null); private final String dateFormat; @@ -44,7 +44,7 @@ public class DateSerDesc implements GsonTypeAdapter { * * @param dateFormat 日期格式 */ - public DateSerDesc(final String dateFormat) { + public DateGsonTypeAdapter(final String dateFormat) { this.dateFormat = dateFormat; } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/GsonEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/GsonEngine.java index 22abb820c..f0adab467 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/GsonEngine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/gson/GsonEngine.java @@ -115,7 +115,7 @@ public class GsonEngine extends AbstractJSONEngine implements Wrapper { */ private void registerDate(final GsonBuilder builder, final String dateFormat){ // java date - builder.registerTypeHierarchyAdapter(Date.class, new DateSerDesc(dateFormat)); + builder.registerTypeHierarchyAdapter(Date.class, new DateGsonTypeAdapter(dateFormat)); builder.registerTypeHierarchyAdapter(TimeZone.class, TimeZoneGsonTypeAdapter.INSTANCE); // java.time diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/DateMoshiAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/DateMoshiAdapter.java new file mode 100644 index 000000000..8baa449b5 --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/DateMoshiAdapter.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.json.engine.moshi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.reflect.TypeUtil; +import org.dromara.hutool.core.text.StrUtil; + +import java.io.IOException; +import java.util.Date; + +/** + * 日期格式化适配器,用于moshi序列化与反序列化日期 + * + * @author looly + * @since 6.0.0 + */ +public class DateMoshiAdapter extends JsonAdapter { + + /** + * 创建工厂 + * + * @param dateFormat 日期格式 + * @return 创建工厂 + */ + public static Factory createFactory(final String dateFormat){ + return (type, set, moshi) -> { + if(Date.class.isAssignableFrom(TypeUtil.getClass(type))){ + return new DateMoshiAdapter(dateFormat); + } + + return null; + }; + } + + private final String dateFormat; + + /** + * 构造 + * + * @param dateFormat 日期格式 + */ + public DateMoshiAdapter(final String dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void toJson(final JsonWriter jsonWriter, final Date date) throws IOException { + if(null == date){ + jsonWriter.nullValue(); + return; + } + + if(StrUtil.isEmpty(dateFormat)){ + jsonWriter.value(date.getTime()); + }else{ + jsonWriter.value(DateUtil.format(date, dateFormat)); + } + jsonWriter.flush(); + } + + @Override + public Date fromJson(final JsonReader jsonReader) throws IOException { + return StrUtil.isEmpty(dateFormat) ? + DateUtil.date(jsonReader.nextLong()) : + DateUtil.parse(jsonReader.nextString(), dateFormat); + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/MoshiEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/MoshiEngine.java new file mode 100644 index 000000000..a26b81630 --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/MoshiEngine.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.json.engine.moshi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import okio.BufferedSink; +import okio.BufferedSource; +import okio.Okio; +import org.dromara.hutool.core.io.stream.ReaderInputStream; +import org.dromara.hutool.core.io.stream.WriterOutputStream; +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.lang.wrapper.Wrapper; +import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.core.util.ObjUtil; +import org.dromara.hutool.json.JSONException; +import org.dromara.hutool.json.engine.AbstractJSONEngine; +import org.dromara.hutool.json.engine.JSONEngineConfig; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * Moshi引擎实现 + * + * @author looly + * @since 6.0.0 + */ +public class MoshiEngine extends AbstractJSONEngine implements Wrapper { + + private Moshi moshi; + + /** + * 构造 + */ + public MoshiEngine() { + // issue#IABWBL JDK8下,在IDEA旗舰版加载Spring boot插件时,启动应用不会检查字段类是否存在 + // 此处构造时调用下这个类,以便触发类是否存在的检查 + Assert.notNull(Moshi.class); + } + + @Override + public Moshi getRaw() { + initEngine(); + return this.moshi; + } + + @SuppressWarnings("unchecked") + @Override + public void serialize(final Object bean, final Writer writer) { + initEngine(); + final BufferedSink sink = Okio.buffer(Okio.sink(new WriterOutputStream(writer, CharsetUtil.UTF_8))); + JsonAdapter adapter = (JsonAdapter) this.moshi.adapter(bean.getClass()); + + if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){ + adapter = adapter.indent(" "); + } + if(!ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isIgnoreNullValue, true)){ + adapter = adapter.serializeNulls(); + } + + try { + adapter.toJson(sink, bean); + } catch (final IOException e) { + throw new JSONException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public T deserialize(final Reader reader, final Object type) { + initEngine(); + final BufferedSource source = Okio.buffer(Okio.source(new ReaderInputStream(reader, CharsetUtil.UTF_8))); + final JsonAdapter adapter = this.moshi.adapter((Type) type); + try { + return (T) adapter.fromJson(source); + } catch (final IOException e) { + throw new JSONException(e); + } + } + + @Override + protected void reset() { + this.moshi = null; + } + + @Override + protected void initEngine() { + if (null != this.moshi) { + return; + } + + // 自定义配置 + final JSONEngineConfig config = ObjUtil.defaultIfNull(this.config, JSONEngineConfig::of); + final Moshi.Builder builder = new Moshi.Builder(); + + // 注册日期相关序列化描述 + final String dateFormat = config.getDateFormat(); + registerDate(builder, dateFormat); + + this.moshi = builder.build(); + } + + /** + * 注册日期相关序列化描述 + * + * @param builder Gson构造器 + * @param dateFormat 日期格式 + */ + private void registerDate(final Moshi.Builder builder, final String dateFormat){ + builder.add(DateMoshiAdapter.createFactory(dateFormat)); + builder.add(LocalDateTime.class, new TemporalMoshiAdapter(LocalDateTime.class, dateFormat)); + builder.add(LocalDate.class, new TemporalMoshiAdapter(LocalDate.class, dateFormat)); + builder.add(LocalTime.class, new TemporalMoshiAdapter(LocalTime.class, dateFormat)); + builder.add(TimeZoneMoshiAdapter.FACTORY); + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TemporalMoshiAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TemporalMoshiAdapter.java new file mode 100644 index 000000000..96f5ee0db --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TemporalMoshiAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.json.engine.moshi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; +import org.dromara.hutool.core.convert.ConvertUtil; +import org.dromara.hutool.core.date.TimeUtil; +import org.dromara.hutool.core.text.StrUtil; + +import java.io.IOException; +import java.time.temporal.TemporalAccessor; + +/** + * 时间类型适配器,用于处理时间类型的序列化与反序列化 + * + * @author looly + * @since 6.0.0 + */ +public class TemporalMoshiAdapter extends JsonAdapter { + + private final Class type; + private final String dateFormat; + + /** + * 构造 + * + * @param type 时间类型 + * @param dateFormat 日期格式 + */ + public TemporalMoshiAdapter(final Class type, final String dateFormat) { + this.type = type; + this.dateFormat = dateFormat; + } + + @Override + public void toJson(final JsonWriter jsonWriter, final TemporalAccessor src) throws IOException { + if (null == src){ + jsonWriter.nullValue(); + return; + } + + if (StrUtil.isEmpty(dateFormat)) { + jsonWriter.value(TimeUtil.toEpochMilli(src)); + } else { + jsonWriter.value(TimeUtil.format(src, dateFormat)); + } + jsonWriter.flush(); + } + + @Override + public TemporalAccessor fromJson(final JsonReader jsonReader) throws IOException { + return StrUtil.isEmpty(dateFormat) ? + ConvertUtil.convert(this.type, jsonReader.nextLong()) : + ConvertUtil.convert(this.type, TimeUtil.parse(jsonReader.nextString(), dateFormat)); + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TimeZoneMoshiAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TimeZoneMoshiAdapter.java new file mode 100644 index 000000000..c1dd268db --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/TimeZoneMoshiAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.json.engine.moshi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; +import com.squareup.moshi.Moshi; +import org.dromara.hutool.core.reflect.TypeUtil; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; +import java.util.TimeZone; + +/** + * TimeZone类型适配器 + * + * @author Looly + */ +public class TimeZoneMoshiAdapter extends JsonAdapter { + + /** + * 创建工厂 + */ + public static final Factory FACTORY = new Factory() { + @Override + public JsonAdapter create(final Type type, final Set set, final Moshi moshi) { + if(TimeZone.class.isAssignableFrom(TypeUtil.getClass(type))){ + return INSTANCE; + } + return null; + } + }; + + /** + * 单例 + */ + public static final TimeZoneMoshiAdapter INSTANCE = new TimeZoneMoshiAdapter(); + + @Override + public void toJson(final JsonWriter jsonWriter, final TimeZone timeZone) throws IOException { + if(null == timeZone){ + jsonWriter.nullValue(); + return; + } + jsonWriter.value(timeZone.getID()); + jsonWriter.flush(); + } + + @Override + public TimeZone fromJson(final JsonReader jsonReader) throws IOException { + return TimeZone.getTimeZone(jsonReader.nextString()); + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/package-info.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/package-info.java new file mode 100644 index 000000000..227f52e13 --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/moshi/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Moshi引擎实现
+ * https://github.com/square/moshi + * + * @author Looly + * @since 6.0.0 + */ +package org.dromara.hutool.json.engine.moshi; diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLSerializer.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLSerializer.java index c0a59a928..e549bb243 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLSerializer.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLSerializer.java @@ -65,7 +65,7 @@ public class JSONXMLSerializer { * @return A string. * @throws JSONException JSON解析异常 */ - public static String toXml(JSON json, final String tagName, final String... contentKeys) throws JSONException { + public static String toXml(final JSON json, final String tagName, final String... contentKeys) throws JSONException { if (null == json) { return null; } diff --git a/hutool-json/src/main/resources/META-INF/services/org.dromara.hutool.json.engine.JSONEngine b/hutool-json/src/main/resources/META-INF/services/org.dromara.hutool.json.engine.JSONEngine index c65704da6..9ddedb1dd 100644 --- a/hutool-json/src/main/resources/META-INF/services/org.dromara.hutool.json.engine.JSONEngine +++ b/hutool-json/src/main/resources/META-INF/services/org.dromara.hutool.json.engine.JSONEngine @@ -17,4 +17,5 @@ org.dromara.hutool.json.engine.jackson.JacksonEngine org.dromara.hutool.json.engine.gson.GsonEngine org.dromara.hutool.json.engine.fastjson.FastJSON2Engine +org.dromara.hutool.json.engine.moshi.MoshiEngine org.dromara.hutool.json.engine.HutoolJSONEngine diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java index 44036acd0..b7bedb93b 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java @@ -28,7 +28,7 @@ import java.util.TimeZone; public class JSONEngineTest { - private final String[] engineNames = {"jackson", "gson", "fastjson", "hutool"}; + private final String[] engineNames = {"jackson", "gson", "fastjson", "moshi", "hutool"}; @Test void writeDateFormatTest() {