用gson对abstract class (de)serialize
用gson对abstract class (de)serialize时,不能直接
Gson gson = new Gson();
List<BaseClass> list = gson.fromJson(string, new TypeToken<List<BaseClass>>(){}.getType());
否则会报错:
java.lang.RuntimeException: Failed to invoke public xxx.BaseClass() with no args
可以用RunTimeTypeAdapterFactor:
RuntimeTypeAdapterFactory<BaseClass> typeAdapterFactory = RuntimeTypeAdapterFactory
.of\(BaseClass.class\).registerSubtype\(SubClass1.class\).registerSubtype\(SubClass2.class\);
Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeAdapterFactory).create();
List<BaseClass> list = gson.fromJson(string, new TypeToken<List<BaseClass>>(){}.getType());
ref: https://futurestud.io/tutorials/how-to-deserialize-a-list-of-polymorphic-objects-with-gson
但是运行时还是报错:
com.google.gson.JsonParseException: cannot deserialize class xxx.BaseClass because it does not define a field named type
解决方法:
在RunTimeTypeAdapterFactor.java中把 if (type.getRawType() != baseType)改为:
if (null == type || !baseType.isAssignableFrom(type.getRawType()))
ref: https://github.com/google/gson/issues/712
RunTimeTypeAdapterFactor.java: (https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java)
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>\(\);
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>\(\);
private RuntimeTypeAdapterFactory\(Class<?> baseType, String typeFieldName\) {
if \(typeFieldName == null \|\| baseType == null\) {
throw new NullPointerException\(\);
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
}
/\*\*
\* Creates a new runtime type adapter using for {@code baseType} using {@code
\* typeFieldName} as the type field name. Type field names are case sensitive.
\*/
public static <T> RuntimeTypeAdapterFactory<T> of\(Class<T> baseType, String typeFieldName\) {
return new RuntimeTypeAdapterFactory<T>\(baseType, typeFieldName\);
}
/\*\*
\* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
\* the type field name.
\*/
public static <T> RuntimeTypeAdapterFactory<T> of\(Class<T> baseType\) {
return new RuntimeTypeAdapterFactory<T>\(baseType, "type"\);
}
/\*\*
\* Registers {@code type} identified by {@code label}. Labels are case
\* sensitive.
\*
\* @throws IllegalArgumentException if either {@code type} or {@code label}
\* have already been registered on this type adapter.
\*/
public RuntimeTypeAdapterFactory<T> registerSubtype\(Class<? extends T> type, String label\) {
if \(type == null \|\| label == null\) {
throw new NullPointerException\(\);
}
if \(subtypeToLabel.containsKey\(type\) \|\| labelToSubtype.containsKey\(label\)\) {
throw new IllegalArgumentException\("types and labels must be unique"\);
}
labelToSubtype.put\(label, type\);
subtypeToLabel.put\(type, label\);
return this;
}
/\*\*
\* Registers {@code type} identified by its {@link Class\#getSimpleName simple
\* name}. Labels are case sensitive.
\*
\* @throws IllegalArgumentException if either {@code type} or its simple name
\* have already been registered on this type adapter.
\*/
public RuntimeTypeAdapterFactory<T> registerSubtype\(Class<? extends T> type\) {
return registerSubtype\(type, type.getSimpleName\(\)\);
}
public <R> TypeAdapter<R> create\(Gson gson, TypeToken<R> type\) {
if \(null == type \|\| !baseType.isAssignableFrom\(type.getRawType\(\)\)\) {
return null;
}
final Map<String, TypeAdapter<?>> labelToDelegate
= new LinkedHashMap<String, TypeAdapter<?>>\(\);
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
= new LinkedHashMap<Class<?>, TypeAdapter<?>>\(\);
for \(Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet\(\)\) {
TypeAdapter<?> delegate = gson.getDelegateAdapter\(this, TypeToken.get\(entry.getValue\(\)\)\);
labelToDelegate.put\(entry.getKey\(\), delegate\);
subtypeToDelegate.put\(entry.getValue\(\), delegate\);
}
return new TypeAdapter<R>\(\) {
@Override public R read\(JsonReader in\) throws IOException {
JsonElement jsonElement = Streams.parse\(in\);
JsonElement labelJsonElement = jsonElement.getAsJsonObject\(\).remove\(typeFieldName\);
if \(labelJsonElement == null\) {
throw new JsonParseException\("cannot deserialize " + baseType
+ " because it does not define a field named " + typeFieldName\);
}
String label = labelJsonElement.getAsString\(\);
@SuppressWarnings\("unchecked"\) // registration requires that subtype extends T
TypeAdapter<R> delegate = \(TypeAdapter<R>\) labelToDelegate.get\(label\);
if \(delegate == null\) {
throw new JsonParseException\("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?"\);
}
return delegate.fromJsonTree\(jsonElement\);
}
@Override public void write\(JsonWriter out, R value\) throws IOException {
Class<?> srcType = value.getClass\(\);
String label = subtypeToLabel.get\(srcType\);
@SuppressWarnings\("unchecked"\) // registration requires that subtype extends T
TypeAdapter<R> delegate = \(TypeAdapter<R>\) subtypeToDelegate.get\(srcType\);
if \(delegate == null\) {
throw new JsonParseException\("cannot serialize " + srcType.getName\(\)
+ "; did you forget to register a subtype?"\);
}
JsonObject jsonObject = delegate.toJsonTree\(value\).getAsJsonObject\(\);
if \(jsonObject.has\(typeFieldName\)\) {
throw new JsonParseException\("cannot serialize " + srcType.getName\(\)
+ " because it already defines a field named " + typeFieldName\);
}
JsonObject clone = new JsonObject\(\);
clone.add\(typeFieldName, new JsonPrimitive\(label\)\);
for \(Map.Entry<String, JsonElement> e : jsonObject.entrySet\(\)\) {
clone.add\(e.getKey\(\), e.getValue\(\)\);
}
Streams.write\(clone, out\);
}
}.nullSafe\(\);
}
}