add moshi

This commit is contained in:
Looly 2024-09-28 22:53:29 +08:00
parent 8eff63bc9f
commit 4db6ac1f34
12 changed files with 462 additions and 9 deletions

View File

@ -473,6 +473,22 @@ public class TypeUtil {
return parameterizedType; return parameterizedType;
} }
/**
* 创建泛型对象例如
*
* <pre>{@code
* List<String> list = TypeUtil.createParameterizedType(List.class, String.class);
* }</pre>
*
* @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 * 获得泛型变量对应的泛型实际类型如果此变量没有对应的实际类型返回null
* *

View File

@ -38,6 +38,10 @@
<bouncycastle.version>1.78.1</bouncycastle.version> <bouncycastle.version>1.78.1</bouncycastle.version>
<jjwt.version>0.12.6</jjwt.version> <jjwt.version>0.12.6</jjwt.version>
<jackson.version>2.17.2</jackson.version> <jackson.version>2.17.2</jackson.version>
<gson.version>2.11.0</gson.version>
<fastjson2.version>2.0.41</fastjson2.version>
<moshi.version>1.15.1</moshi.version>
<jmh.version>1.37</jmh.version>
</properties> </properties>
<dependencies> <dependencies>
@ -69,13 +73,19 @@
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.11.0</version> <version>${gson.version}</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId> <artifactId>fastjson2</artifactId>
<version>2.0.41</version> <version>${fastjson2.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi</artifactId>
<version>1.15.1</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
@ -98,6 +108,45 @@
<version>${jjwt.version}</version> <version>${jjwt.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- 基准性能测试 -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build>
<plugins>
<!-- 添加JMH测试资源 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-jmh-resource</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<directory>src/test/jmh</directory>
<targetPath>my-resources</targetPath>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -30,12 +30,12 @@ import java.util.Date;
* @author Looly * @author Looly
* @since 6.0.0 * @since 6.0.0
*/ */
public class DateSerDesc implements GsonTypeAdapter<Date> { public class DateGsonTypeAdapter implements GsonTypeAdapter<Date> {
/** /**
* 默认日期格式化描述默认为null表示使用时间戳 * 默认日期格式化描述默认为null表示使用时间戳
*/ */
public static final DateSerDesc INSTANCE = new DateSerDesc(null); public static final DateGsonTypeAdapter INSTANCE = new DateGsonTypeAdapter(null);
private final String dateFormat; private final String dateFormat;
@ -44,7 +44,7 @@ public class DateSerDesc implements GsonTypeAdapter<Date> {
* *
* @param dateFormat 日期格式 * @param dateFormat 日期格式
*/ */
public DateSerDesc(final String dateFormat) { public DateGsonTypeAdapter(final String dateFormat) {
this.dateFormat = dateFormat; this.dateFormat = dateFormat;
} }

View File

@ -115,7 +115,7 @@ public class GsonEngine extends AbstractJSONEngine implements Wrapper<Gson> {
*/ */
private void registerDate(final GsonBuilder builder, final String dateFormat){ private void registerDate(final GsonBuilder builder, final String dateFormat){
// java date // java date
builder.registerTypeHierarchyAdapter(Date.class, new DateSerDesc(dateFormat)); builder.registerTypeHierarchyAdapter(Date.class, new DateGsonTypeAdapter(dateFormat));
builder.registerTypeHierarchyAdapter(TimeZone.class, TimeZoneGsonTypeAdapter.INSTANCE); builder.registerTypeHierarchyAdapter(TimeZone.class, TimeZoneGsonTypeAdapter.INSTANCE);
// java.time // java.time

View File

@ -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<Date> {
/**
* 创建工厂
*
* @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);
}
}

View File

@ -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<Moshi> {
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<Object> adapter = (JsonAdapter<Object>) 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> T deserialize(final Reader reader, final Object type) {
initEngine();
final BufferedSource source = Okio.buffer(Okio.source(new ReaderInputStream(reader, CharsetUtil.UTF_8)));
final JsonAdapter<Object> 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);
}
}

View File

@ -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<TemporalAccessor> {
private final Class<? extends TemporalAccessor> type;
private final String dateFormat;
/**
* 构造
*
* @param type 时间类型
* @param dateFormat 日期格式
*/
public TemporalMoshiAdapter(final Class<? extends TemporalAccessor> 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));
}
}

View File

@ -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<TimeZone> {
/**
* 创建工厂
*/
public static final Factory FACTORY = new Factory() {
@Override
public JsonAdapter<?> create(final Type type, final Set<? extends Annotation> 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());
}
}

View File

@ -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引擎实现<br>
* https://github.com/square/moshi
*
* @author Looly
* @since 6.0.0
*/
package org.dromara.hutool.json.engine.moshi;

View File

@ -65,7 +65,7 @@ public class JSONXMLSerializer {
* @return A string. * @return A string.
* @throws JSONException JSON解析异常 * @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) { if (null == json) {
return null; return null;
} }

View File

@ -17,4 +17,5 @@
org.dromara.hutool.json.engine.jackson.JacksonEngine org.dromara.hutool.json.engine.jackson.JacksonEngine
org.dromara.hutool.json.engine.gson.GsonEngine org.dromara.hutool.json.engine.gson.GsonEngine
org.dromara.hutool.json.engine.fastjson.FastJSON2Engine org.dromara.hutool.json.engine.fastjson.FastJSON2Engine
org.dromara.hutool.json.engine.moshi.MoshiEngine
org.dromara.hutool.json.engine.HutoolJSONEngine org.dromara.hutool.json.engine.HutoolJSONEngine

View File

@ -28,7 +28,7 @@ import java.util.TimeZone;
public class JSONEngineTest { public class JSONEngineTest {
private final String[] engineNames = {"jackson", "gson", "fastjson", "hutool"}; private final String[] engineNames = {"jackson", "gson", "fastjson", "moshi", "hutool"};
@Test @Test
void writeDateFormatTest() { void writeDateFormatTest() {