fix(serialize-databind): support custom map subtypes and collections
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
3cd8aba2d1
commit
1a09294690
|
@ -0,0 +1,89 @@
|
||||||
|
package io.gitlab.jfronny.commons.serialize.databind.impl.adapter;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.Token;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.databind.ObjectMapper;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.databind.TypeAdapterFactory;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.databind.api.TypeAdapter;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.databind.api.TypeUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(ObjectMapper mapper, TypeToken<T> type) {
|
||||||
|
Class<? super T> rawType = type.getRawType();
|
||||||
|
if (!Collection.class.isAssignableFrom(rawType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type elementType = TypeUtils.getCollectionElementType(type.getType(), rawType);
|
||||||
|
TypeAdapter<?> elementTypeAdapter = mapper.getAdapter(TypeToken.get(elementType));
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
|
||||||
|
TypeAdapter<T> result = new Adapter(mapper, elementType, elementTypeAdapter, rawType);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_LIST = Set.of(List.class, AbstractList.class, SequencedCollection.class, Collection.class, AbstractCollection.class, Iterable.class);
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_LINKED = Set.of(Deque.class, Queue.class, AbstractSequentialList.class);
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_SET = Set.of(Set.class, SequencedSet.class, AbstractSet.class);
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_SSET = Set.of(SortedSet.class, NavigableSet.class);
|
||||||
|
|
||||||
|
private final TypeAdapter<E> elementTypeAdapter;
|
||||||
|
private final Class<?> implClass;
|
||||||
|
|
||||||
|
public Adapter(
|
||||||
|
ObjectMapper context,
|
||||||
|
Type elementType,
|
||||||
|
TypeAdapter<E> elementTypeAdapter, Class<?> implClass) {
|
||||||
|
this.elementTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, elementTypeAdapter, elementType);
|
||||||
|
this.implClass = RAW_TYPES_LIST.contains(implClass) ? ArrayList.class
|
||||||
|
: RAW_TYPES_LINKED.contains(implClass) ? LinkedList.class
|
||||||
|
: RAW_TYPES_SET.contains(implClass) ? LinkedHashSet.class
|
||||||
|
: RAW_TYPES_SSET.contains(implClass) ? TreeSet.class
|
||||||
|
: implClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(Collection<E> value, Writer writer) throws TEx, MalformedDataException {
|
||||||
|
if (value == null) {
|
||||||
|
writer.nullValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.beginArray();
|
||||||
|
for (E element : value) {
|
||||||
|
elementTypeAdapter.serialize(element, writer);
|
||||||
|
}
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> Collection<E> deserialize(Reader reader) throws TEx, MalformedDataException {
|
||||||
|
if (reader.peek() == Token.NULL) {
|
||||||
|
reader.nextNull();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<E> collection = (Collection<E>) TypeUtils.instantiate(implClass).orElseThrow(() -> new MalformedDataException("Cannot instantiate collection of type " + implClass.getName()));
|
||||||
|
if (!reader.isLenient() || reader.peek() == Token.BEGIN_ARRAY) {
|
||||||
|
reader.beginArray();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
E instance = elementTypeAdapter.deserialize(reader);
|
||||||
|
collection.add(instance);
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
} else {
|
||||||
|
// Coerce
|
||||||
|
collection.add(elementTypeAdapter.deserialize(reader));
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,9 @@ public class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MapTypeAdapter<K, V> extends TypeAdapter<Map<K, V>> {
|
private static class MapTypeAdapter<K, V> extends TypeAdapter<Map<K, V>> {
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_L = Set.of(Map.class, SequencedMap.class, AbstractMap.class);
|
||||||
|
private static final Set<Class<?>> RAW_TYPES_S = Set.of(SortedMap.class, NavigableMap.class);
|
||||||
|
|
||||||
private final TypeAdapter<K> keyTypeAdapter;
|
private final TypeAdapter<K> keyTypeAdapter;
|
||||||
private final TypeAdapter<V> valueTypeAdapter;
|
private final TypeAdapter<V> valueTypeAdapter;
|
||||||
private final Class<?> implClass;
|
private final Class<?> implClass;
|
||||||
|
@ -50,7 +53,9 @@ public class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
Class<?> implClass) {
|
Class<?> implClass) {
|
||||||
this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
|
this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
|
||||||
this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType);
|
this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType);
|
||||||
this.implClass = implClass.equals(Map.class) ? LinkedHashMap.class : implClass;
|
this.implClass = RAW_TYPES_L.contains(implClass) ? LinkedHashMap.class
|
||||||
|
: RAW_TYPES_S.contains(implClass) ? TreeMap.class
|
||||||
|
: implClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.SerializationAdapterTypeAdapterFactory
|
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.SerializationAdapterTypeAdapterFactory
|
||||||
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.ArrayTypeAdapterFactory
|
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.ArrayTypeAdapterFactory
|
||||||
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.EnumTypeAdapterFactory
|
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.EnumTypeAdapterFactory
|
||||||
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.MapTypeAdapterFactory
|
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.MapTypeAdapterFactory
|
||||||
|
io.gitlab.jfronny.commons.serialize.databind.impl.adapter.CollectionTypeAdapterFactory
|
|
@ -11,10 +11,12 @@ import io.gitlab.jfronny.commons.serialize.emulated.EmulatedWriter;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
public class MapAdapterTest {
|
public class ObjectMapperTest {
|
||||||
@Test
|
@Test
|
||||||
public void simpleMapTEst() throws MalformedDataException {
|
public void simpleMapTEst() throws MalformedDataException {
|
||||||
DataElement.Object de = new DataElement.Object();
|
DataElement.Object de = new DataElement.Object();
|
||||||
|
@ -58,4 +60,44 @@ public class MapAdapterTest {
|
||||||
assertEquals(de, ew.get());
|
assertEquals(de, ew.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSet() throws MalformedDataException {
|
||||||
|
DataElement.Array de = new DataElement.Array();
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value"));
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value2"));
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value3"));
|
||||||
|
ObjectMapper om = new ObjectMapper();
|
||||||
|
TypeAdapter<Set<String>> adapter = om.getAdapter(new TypeToken<>() {});
|
||||||
|
Set<String> set;
|
||||||
|
try (EmulatedReader er = new EmulatedReader(de)) {
|
||||||
|
set = adapter.deserialize(er);
|
||||||
|
}
|
||||||
|
assertEquals(3, set.size());
|
||||||
|
assertEquals(Set.of("value", "value2", "value3"), set);
|
||||||
|
try (EmulatedWriter ew = new EmulatedWriter()) {
|
||||||
|
adapter.serialize(set, ew);
|
||||||
|
assertEquals(de, ew.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSet2() throws MalformedDataException {
|
||||||
|
DataElement.Array de = new DataElement.Array();
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value"));
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value2"));
|
||||||
|
de.elements().add(new DataElement.Primitive.String("value3"));
|
||||||
|
ObjectMapper om = new ObjectMapper();
|
||||||
|
TypeAdapter<TreeSet<String>> adapter = om.getAdapter(new TypeToken<>() {});
|
||||||
|
TreeSet<String> set;
|
||||||
|
try (EmulatedReader er = new EmulatedReader(de)) {
|
||||||
|
set = adapter.deserialize(er);
|
||||||
|
}
|
||||||
|
assertEquals(3, set.size());
|
||||||
|
assertEquals(Set.of("value", "value2", "value3"), set);
|
||||||
|
try (EmulatedWriter ew = new EmulatedWriter()) {
|
||||||
|
adapter.serialize(set, ew);
|
||||||
|
assertEquals(de, ew.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue