Обычно реализации коллекций «берут» тип из объявления поля коллекции, а не из данного элемента в List
/ Set
/ и т. Д.Нам нужно написать собственный сериализатор, который для каждого элемента находит сериализатор и использует его.Простая реализация:
class TypeAwareListJsonSeserializer implements JsonSerializer<List<?>> {
@Override
public JsonElement serialize(List<?> src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
return JsonNull.INSTANCE;
}
JsonArray array = new JsonArray();
for (Object item : src) {
JsonElement jsonElement = context.serialize(item, item.getClass());
array.add(jsonElement);
}
return array;
}
}
И вот как мы можем использовать это:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
public class GsonApp {
public static void main(String[] args) throws Exception {
List<BaseBean> children = Arrays.asList(new BaseBean(), new ChildBean(), new ChildBean2());
BaseBeanHolder baseHolder = new BaseBeanHolder(children);
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
System.out.println(gson.toJson(baseHolder));
}
}
class BaseBean {
String baseField = "base";
}
class ChildBean extends BaseBean {
String childField = "child";
}
class ChildBean2 extends BaseBean {
int bean2Int = 356;
}
class BaseBeanHolder {
@JsonAdapter(TypeAwareListJsonSeserializer.class)
private List<? extends BaseBean> beans;
// getters, setters, toString
}
Над отпечатками кода:
{
"beans": [
{
"baseField": "base"
},
{
"childField": "child",
"baseField": "base"
},
{
"bean2Int": 356,
"baseField": "base"
}
]
}
РЕДАКТИРОВАТЬ
Во время сериализации мы теряем информацию о типе, которая потребуется в процессе десериализации.Я разработал информацию простого типа, которая будет храниться во время сериализации и использоваться при десериализации.Это может выглядеть следующим образом:
class TypeAwareListJsonAdapter implements JsonSerializer<List<?>>, JsonDeserializer<List<?>> {
private final String typeProperty = "@type";
@Override
public JsonElement serialize(List<?> src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
return JsonNull.INSTANCE;
}
JsonArray array = new JsonArray();
for (Object item : src) {
JsonObject jsonElement = (JsonObject) context.serialize(item, item.getClass());
jsonElement.addProperty(typeProperty, item.getClass().getSimpleName());
array.add(jsonElement);
}
return array;
}
@Override
public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Type elementType = $Gson$Types.getCollectionElementType(typeOfT, List.class);
if (json instanceof JsonArray) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return Collections.emptyList();
}
final List<?> suites = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
JsonObject jsonElement = (JsonObject) array.get(i);
String simpleName = jsonElement.get(typeProperty).getAsString();
suites.add(context.deserialize(jsonElement, getClass(simpleName, elementType)));
}
return suites;
}
return Collections.emptyList();
}
private Type getClass(String simpleName, Type defaultType) {
try {
// you can use mapping or something else...
return Class.forName("com.model." + simpleName);
} catch (ClassNotFoundException e) {
return defaultType;
}
}
}
Самая большая проблема заключается в том, как сопоставить классы значениям JSON
.Мы можем использовать простое имя класса или предоставить Map<String, Class>
и использовать его.Теперь мы можем использовать его, как указано выше.Пример приложения печатается сейчас:
{
"beans": [
{
"baseField": "base",
"@type": "BaseBean"
},
{
"childField": "child",
"baseField": "base",
"@type": "ChildBean"
},
{
"bean2Int": 356,
"baseField": "base",
"@type": "ChildBean2"
}
]
}
BaseBean{baseField='base'}
ChildBean{baseField='base', childField='child'}
ChildBean2{baseField='base', bean2Int=356}