Я сомневаюсь, что любая из библиотек будет поддерживать это "из коробки", потому что их единственная ответственность связана с сериализацией / десериализацией.Следовательно, чтобы заставить любой сериализатор работать так, как вы хотите, гораздо лучше создать объект-макет, а затем попытаться адаптировать стратегию-макет для конкретной библиотеки.
Прежде всего, давайте простосоздайте простой служебный класс для типов:
final class Types {
private Types() {
}
static <T> Class<T> typeToClass(final Type type) {
final Class<?> clazz;
if ( type instanceof Class ) {
clazz = (Class<?>) type;
} else if ( type instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
clazz = typeToClass(parameterizedType.getRawType());
} else {
throw new AssertionError(type);
}
@SuppressWarnings("unchecked")
final Class<T> castClass = (Class<T>) clazz;
return castClass;
}
}
Приведенный выше метод отвечает только за разрешение класса из типа (по крайней мере, он пытается это сделать).Классы являются типами, но типы не обязательно являются классами.Ну, это их система типов Java.
Следующий класс отвечает за насмешку над объектом по типу.Это немного сложно, но комментарии проливают некоторый свет (ImmutableMap
и ImmutableList
взяты из Google Guava).
final class Mock {
// Cache immutable primitives and wrappers. It's safe
private static final Optional<Byte> defaultByte = Optional.of((byte) 0);
private static final Optional<Short> defaultShort = Optional.of((short) 0);
private static final Optional<Integer> defaultInteger = Optional.of(0);
private static final Optional<Long> defaultLong = Optional.of(0L);
private static final Optional<Float> defaultFloat = Optional.of(0F);
private static final Optional<Double> defaultDouble = Optional.of(0D);
private static final Optional<Character> defaultCharacter = Optional.of('\u0000');
private static final Optional<Boolean> defaultBoolean = Optional.of(false);
private static final Optional<String> defaultString = Optional.of("");
// This is a simple map that can return a value by a known type
private static final Map<Class<?>, Optional<?>> defaultObjectsByIndex = ImmutableMap.<Class<?>, Optional<?>>builder()
.put(byte.class, defaultByte).put(Byte.class, defaultByte)
.put(short.class, defaultShort).put(Short.class, defaultShort)
.put(int.class, defaultInteger).put(Integer.class, defaultInteger)
.put(long.class, defaultLong).put(Long.class, defaultLong)
.put(float.class, defaultFloat).put(Float.class, defaultFloat)
.put(double.class, defaultDouble).put(Double.class, defaultDouble)
.put(char.class, defaultCharacter).put(Character.class, defaultCharacter)
.put(boolean.class, defaultBoolean).put(Boolean.class, defaultBoolean)
.put(String.class, defaultString)
.build();
// Unlike the previous map, searching for an object takes a linear type
// The first-best candidate will be used
// The ifAssignable method is declared below
private static final Collection<? extends Function<? super Type, Optional<?>>> defaultObjectsByFirstBest = ImmutableList.<Function<? super Type, Optional<?>>>builder()
.add(ifAssignable(LinkedList.class, LinkedList::new))
.add(ifAssignable(ArrayList.class, ArrayList::new))
.add(ifAssignable(List.class, ArrayList::new))
.add(ifAssignable(TreeSet.class, TreeSet::new))
.add(ifAssignable(LinkedHashSet.class, LinkedHashSet::new))
.add(ifAssignable(HashSet.class, HashSet::new))
.add(ifAssignable(Set.class, HashSet::new))
.add(ifAssignable(TreeMap.class, TreeMap::new))
.add(ifAssignable(LinkedHashMap.class, LinkedHashMap::new))
.add(ifAssignable(HashMap.class, HashMap::new))
.add(ifAssignable(Map.class, HashMap::new))
.build();
private Mock() {
}
static <T> T create(
final Type type,
final Predicate<? super Type> isTypeSupported,
final Predicate<? super Field> isFieldSupported,
final Function<? super Class<T>, ? extends T> objectCreator,
final BiFunction<Object, Type, Object> postProcess
)
throws Exception {
return create(type, Mock::supplyDefaultInstance, isTypeSupported, isFieldSupported, objectCreator, postProcess);
}
static <T> T create(
final Type type, // Used to instantiate an object
final Function<? super Type, Optional<?>> defaultInstanceSupplier, // Used in order to supply a default object by type
final Predicate<? super Type> isTypeSupported, // Not all types can be serialized
final Predicate<? super Field> isFieldSupported, // Not all fields can be serialized
final Function<? super Class<T>, ? extends T> objectCreator, // If no any default object can be supplied, try to ask for it from elsewhere
final BiFunction<Object, Type, Object> postProcess // This is what is what used to post-process the result object
)
throws Exception {
// Not something we want to support?
if ( !isTypeSupported.test(type) ) {
return null;
}
// Check if we can provide a default value
final Optional<?> maybeT = defaultInstanceSupplier.apply(type);
if ( maybeT.isPresent() ) {
@SuppressWarnings("unchecked")
final T castT = (T) postProcess.apply(maybeT.get(), type);
return castT;
}
final Class<T> clazz = Types.typeToClass(type);
// No? Then let's try instantiate it
final T newT = objectCreator.apply(clazz);
// And iterate it from bottom to top to super classes (java.lang.Object does not have fields)
for ( Class<?> i = clazz; i != null && i != Object.class; i = i.getSuperclass() ) {
for ( final Field field : i.getDeclaredFields() ) {
if ( isFieldSupported.test(field) ) {
field.setAccessible(true);
// Recursively do the same for all the fields
final Object value = create(field.getGenericType(), defaultInstanceSupplier, isTypeSupported, isFieldSupported, objectCreator, postProcess);
field.set(newT, value);
}
}
}
// And then do post-processing for these "special" types
@SuppressWarnings("unchecked")
final T castNewT = (T) postProcess.apply(newT, type);
return castNewT;
}
static Optional<?> supplyDefaultInstance(final Type type) {
final Optional<?> defaultValueFromIndex = defaultObjectsByIndex.get(type);
if ( defaultValueFromIndex != null ) {
return defaultValueFromIndex;
}
return defaultObjectsByFirstBest
.stream()
.map(resolver -> resolver.apply(type))
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
}
private static Function<? super Type, Optional<?>> ifAssignable(final Class<?> expectedClass, final Supplier<?> defaultObject) {
return actualClass -> expectedClass.isAssignableFrom(Types.typeToClass(actualClass))
? Optional.of(defaultObject.get())
: Optional.empty();
}
}
Как только вы все это получите, вы можете получить специализацию Gson и выполнять постобработку, например, добавлять фиктивные элементы в коллекции (коллекции по умолчанию являются и должны быть пустыми).
public final class Q50515517 {
private Q50515517() {
}
private static final Gson gson = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.disableHtmlEscaping()
.create();
// Gson can avoid use of constructors
private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
public static void main(final String... args)
throws Exception {
gson.toJson(create(Object3.class, gson, Q50515517::customPostProcess), Object3.class, System.out);
}
// Here we make some Gson adaptations
private static <T> T create(final Type type, final Gson gson, final BiFunction<Object, Type, Object> postProcess)
throws Exception {
final Excluder excluder = gson.excluder();
final Predicate<? super Type> isClassSupported = t -> !excluder.excludeClass(Types.typeToClass(t), true);
final Predicate<? super Field> isFieldSupported = field -> !excluder.excludeField(field, true);
return Mock.create(type, Mock::supplyDefaultInstance, isClassSupported, isFieldSupported, Q50515517::createUnsafely, postProcess);
}
private static <T> T createUnsafely(final Class<T> clazz) {
try {
return unsafeAllocator.newInstance(clazz);
} catch ( final Exception ex ) {
throw new RuntimeException(ex);
}
}
private static Object customPostProcess(final Object object, final Type type) {
if ( object instanceof Collection ) {
if ( type instanceof ParameterizedType ) {
@SuppressWarnings("unchecked")
final Collection<Object> collection = (Collection<Object>) object;
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Type elementType = parameterizedType.getActualTypeArguments()[0];
try {
final Object newElement = create(elementType, gson, Q50515517::customPostProcess);
collection.add(newElement); // This is where we add a mock element to the collection
return object;
} catch ( final Exception ex ) {
throw new RuntimeException(ex);
}
}
}
return object;
}
}
Таким образом, это решение поддерживает:
- Классыбез конструкторов по умолчанию
- Поля иерархии классов объектов
- Стратегии исключения
- Интерфейсы
- etc
Примеры классов из вышеприведенного теста:
class Object1 {
String s1;
}
class Object2
extends Object1 {
String s2;
}
class Object3
extends Object2 {
String s3;
List<Object4> lo4;
}
class Object4 {
String s4a;
String s4b;
}
Вывод:
{
"s3": "",
"lo4": [
{
"s4a": "",
"s4b": ""
}
],
"s2": "",
"s1": ""
}