您不需要那么多类型适配器,因为您可以将常见的反序列化逻辑合并到为从该后端遇到的每种有问题的文字类型(就像您在问题中发布的那样)设计的类型适配器中。
类型适配器是独立创建的,不是在类型适配器工厂中创建的,通常适合简单的情况。工厂提供对共享 context Gson 实例(您配置然后使用的实例)的访问,并提供一个具体类型来构建类型适配器(这是“T.class”可以使用的地方周围)。
public abstract class AbstractQuirkyTypeAdapterFactory<T, C>
implements TypeAdapterFactory {
protected abstract boolean supports(@Nonnull TypeToken<?> typeToken);
@Nullable
protected abstract C createContext(@Nonnull TypeToken<?> typeToken);
@Nullable
protected abstract T read(@Nullable C context, @Nonnull TypeAdapter<? extends T> delegateAdapter, @Nonnull JsonReader in)
throws IOException;
@Override
@Nullable
public final <U> TypeAdapter<U> create(final Gson gson, final TypeToken<U> typeToken) {
if ( !supports(typeToken) ) {
return null;
}
@SuppressWarnings("unchecked")
final TypeAdapter<T> delegateAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, typeToken);
@Nullable
final C context = createContext(typeToken);
final TypeAdapter<T> quirkyAdapter = new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateAdapter.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
return AbstractQuirkyTypeAdapterFactory.this.read(context, delegateAdapter, in);
}
}
.nullSafe();
@SuppressWarnings("unchecked")
final TypeAdapter<U> typeAdapter = (TypeAdapter<U>) quirkyAdapter;
return typeAdapter;
}
}
public final class QuirkyBooleanTypeAdapterFactory
extends AbstractQuirkyTypeAdapterFactory<Boolean, Void> {
private static final TypeAdapterFactory instance = new QuirkyBooleanTypeAdapterFactory();
private QuirkyBooleanTypeAdapterFactory() {
}
public static TypeAdapterFactory getInstance() {
return instance;
}
@Override
protected boolean supports(@Nonnull final TypeToken<?> typeToken) {
final Class<?> rawType = typeToken.getRawType();
return rawType == boolean.class
|| rawType == Boolean.class;
}
@Nullable
@Override
protected Void createContext(@Nonnull final TypeToken<?> typeToken) {
return null;
}
@Override
@Nullable
@SuppressWarnings("NestedSwitchStatement")
protected Boolean read(@Nullable final Void context, @Nonnull final TypeAdapter<? extends Boolean> delegateAdapter, @Nonnull final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BOOLEAN:
return delegateAdapter.read(in);
case NUMBER:
final int i = in.nextInt();
switch ( i ) {
case 0:
return false;
case 1:
return true;
default:
throw new JsonSyntaxException("Unhandled integer: " + i);
}
case STRING:
final String s = in.nextString();
switch ( s ) {
case "0":
case "false":
case "null":
return false;
case "1":
case "true":
return true;
default:
throw new JsonSyntaxException("Unhandled string: " + s);
}
case NULL:
return null; // TODO or false?
case BEGIN_ARRAY:
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case END_DOCUMENT:
throw new JsonSyntaxException("Unhandled token: " + token);
default:
throw new AssertionError(token);
}
}
}
public final class QuirkyNumberTypeAdapterFactory
extends AbstractQuirkyTypeAdapterFactory<Number, Number> {
private static final TypeAdapterFactory instance = new QuirkyNumberTypeAdapterFactory();
private static final Function<Class<?>, Number> getKnownZero = new ImmutableMap.Builder<Class<?>, Number>()
.put(byte.class, (byte) 0)
.put(Byte.class, (byte) 0)
.put(short.class, (short) 0)
.put(Short.class, (short) 0)
.put(int.class, 0)
.put(Integer.class, 0)
.put(long.class, 0L)
.put(Long.class, 0L)
.put(float.class, 0F)
.put(Float.class, 0F)
.put(double.class, 0D)
.put(Double.class, 0D)
.put(BigInteger.class, BigInteger.ZERO)
.put(BigDecimal.class, BigDecimal.ZERO)
.build()
::get;
private QuirkyNumberTypeAdapterFactory() {
}
public static TypeAdapterFactory getInstance() {
return instance;
}
@Override
@SuppressWarnings("OverlyComplexBooleanExpression")
protected boolean supports(@Nonnull final TypeToken<?> typeToken) {
final Class<?> rawType = typeToken.getRawType();
return Number.class.isAssignableFrom(rawType)
|| rawType == byte.class
|| rawType == short.class
|| rawType == int.class
|| rawType == long.class
|| rawType == float.class
|| rawType == double.class;
}
@Nullable
@Override
protected Number createContext(@Nonnull final TypeToken<?> typeToken) {
return getKnownZero.apply(typeToken.getRawType());
}
@Override
@Nullable
@SuppressWarnings("NestedSwitchStatement")
protected Number read(@Nullable final Number knownZero, @Nonnull final TypeAdapter<? extends Number> delegateAdapter, @Nonnull final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case NUMBER:
return delegateAdapter.read(in);
case STRING:
final String s = in.nextString();
switch ( s ) {
case "null":
case "false":
if ( knownZero == null ) {
return delegateAdapter.read(new JsonReader(new StringReader("0"))); // TODO optimize "constant" reading or cache previously unknown zero
}
return knownZero;
default:
return delegateAdapter.fromJsonTree(new JsonPrimitive(s)); // TODO optimize bypassing the intermediate JSON element
}
case BOOLEAN:
final boolean b = in.nextBoolean();
if ( !b ) {
if ( knownZero == null ) {
return delegateAdapter.read(new JsonReader(new StringReader("0"))); // TODO optimize "constant" reading or cache previously unknown zero
}
return knownZero;
}
throw new JsonSyntaxException("Unhandled boolean: " + b);
case NULL:
return null; // TODO or zero?
case BEGIN_ARRAY:
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case END_DOCUMENT:
throw new JsonSyntaxException("Unhandled token: " + token);
default:
throw new AssertionError(token);
}
}
}
public final class QuirkyCollectionTypeAdapterFactory
extends AbstractQuirkyTypeAdapterFactory<Collection<?>, Void> {
private static final TypeAdapterFactory instance = new QuirkyCollectionTypeAdapterFactory();
private QuirkyCollectionTypeAdapterFactory() {
}
public static TypeAdapterFactory getInstance() {
return instance;
}
@Override
protected boolean supports(@Nonnull final TypeToken<?> typeToken) {
return Collection.class.isAssignableFrom(typeToken.getRawType());
}
@Nullable
@Override
protected Void createContext(@Nonnull final TypeToken<?> typeToken) {
return null;
}
@Override
@Nullable
protected Collection<?> read(@Nullable final Void context, @Nonnull final TypeAdapter<? extends Collection<?>> delegateAdapter,
@Nonnull final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
return delegateAdapter.read(in);
case BOOLEAN:
final boolean b = in.nextBoolean();
if ( !b ) {
return delegateAdapter.read(new JsonReader(new StringReader("[]"))); // TODO optimize "constant" reading (caching is not possible: collections are supposed be new and mutable)
}
throw new JsonSyntaxException("Unhandled boolean: " + b);
case NULL:
return null; // TODO or empty collection?
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case END_DOCUMENT:
throw new JsonSyntaxException("Unhandled token: " + token);
default:
throw new AssertionError(token);
}
}
}
上述方法为所有三种单独的情况实现了模板方法设计模式:布尔值、数字和数组(JSON 数组,但 Java 集合,为简洁起见,我没有包括 Java 数组适配器)。
它们背后的共享逻辑如下:
- 类型适配器工厂检查它是否可以处理给定的类型。
- 如果可以,则向 Gson 请求委托类型适配器,然后处理。
- 创建一个泛型类型适配器,它只是将写入操作委托给原始类型适配器,但读取操作专门用于访问它的每个子类。
- 每个读取操作都会实现简单的 JSON 令牌窥视,以根据您在问题中描述的怪癖来决定如何进一步进行。
具有以下 JSON:
{
"booleans": [
true,
false,
0,
1,
"0",
"1",
"true",
"false",
null,
"null"
],
"numbers": [
42,
"42",
null,
"null",
false,
"false"
],
"arrays": [
[
"foo",
"bar"
],
[],
false,
null
]
}
以下测试通过:
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@ToString
final class Data {
@SerializedName("booleans")
final List<Boolean> booleans;
@SerializedName("numbers")
final List<Number> numbers;
@SerializedName("arrays")
final List<List<String>> arrays;
}
public final class QuirksTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(QuirkyBooleanTypeAdapterFactory.getInstance())
.registerTypeAdapterFactory(QuirkyNumberTypeAdapterFactory.getInstance())
.registerTypeAdapterFactory(QuirkyCollectionTypeAdapterFactory.getInstance())
.create();
@Test
@SuppressWarnings("ReturnOfNull")
public void test()
throws IOException {
try ( final JsonReader jsonReader = open("quirks.json") ) {
final Data data = gson.fromJson(jsonReader, Data.class);
Assertions.assertIterableEquals(
Arrays.asList(true, false, false, true, false, true, true, false, null, false),
data.booleans
);
Assertions.assertIterableEquals(
Arrays.asList(42, 42, null, 0, 0, 0),
data.numbers
.stream()
.map(n -> n != null ? n.intValue() : null)
.collect(Collectors.toList())
);
Assertions.assertIterableEquals(
Arrays.asList(
Arrays.asList("foo", "bar"),
Collections.emptyList(),
Collections.emptyList(),
null
),
data.arrays
);
}
}
}