forked from public-mirrors/ConfigLib
Add support for polymorphic serialization
This commit adds the Polymorphic annotation that can be used on types. Serializers for polymorphic types are not selected based on the compile-time types of configuration elements, but instead are chosen at runtime based on the actual types of their values. This enables adding instances of subclasses / implementations of a polymorphic type to collections.dev
parent
30430527a1
commit
9f4999c726
@ -0,0 +1,62 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated type is polymorphic. Serializers for polymorphic types are not
|
||||
* selected based on the compile-time types of configuration elements, but instead are chosen at
|
||||
* runtime based on the actual types of their values. This enables adding instances of subclasses /
|
||||
* implementations of a polymorphic type to collections. The subtypes must be valid configurations.
|
||||
* <p>
|
||||
* For correct deserialization, if an instance of polymorphic type (or one of its implementations /
|
||||
* subclasses) is serialized, an additional property that holds type information is added to its
|
||||
* serialization.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* // Example 1
|
||||
* @Polymorphic
|
||||
* @Configuration
|
||||
* static abstract class A { ... }
|
||||
*
|
||||
* static final class Impl1 extends A { ... }
|
||||
* static final class Impl2 extends A { ... }
|
||||
*
|
||||
* List<A> as = List.of(new Impl1(...), new Impl2(...), ...);
|
||||
*
|
||||
* // Example 2
|
||||
* @Polymorphic
|
||||
* interface B { ... }
|
||||
*
|
||||
* record Impl1() implements B { ... }
|
||||
*
|
||||
* @Configuration
|
||||
* static final class Impl2 implements B { ... }
|
||||
*
|
||||
* List<B> bs = List.of(new Impl1(...), new Impl2(...), ...);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SerializeWith(serializer = PolymorphicSerializer.class)
|
||||
public @interface Polymorphic {
|
||||
/**
|
||||
* The default name of the property that holds the type information.
|
||||
*/
|
||||
String DEFAULT_PROPERTY = "type";
|
||||
|
||||
/**
|
||||
* Returns the name of the property that holds the type information.
|
||||
* <p>
|
||||
* The property returned by this method must neither be blank nor be the
|
||||
* name of a configuration element.
|
||||
*
|
||||
* @return name of the property that holds the type information
|
||||
* @see String#isBlank()
|
||||
*/
|
||||
String property() default DEFAULT_PROPERTY;
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
||||
private final SerializerContext context;
|
||||
private final Class<?> polymorphicType;
|
||||
private final Polymorphic annotation;
|
||||
|
||||
public PolymorphicSerializer(SerializerContext context) {
|
||||
this.context = context;
|
||||
// we know it's a class because of SerializerSelector#findMetaSerializerOnType
|
||||
this.polymorphicType = (Class<?>) context.annotatedType().getType();
|
||||
this.annotation = polymorphicType.getAnnotation(Polymorphic.class);
|
||||
requireNonBlankProperty();
|
||||
}
|
||||
|
||||
private void requireNonBlankProperty() {
|
||||
if (annotation.property().isBlank()) {
|
||||
String msg = "The @Polymorphic annotation does not allow a blank property name but " +
|
||||
"type '%s' uses one.".formatted(polymorphicType.getName());
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<?, ?> serialize(Object element) {
|
||||
// this cast won't cause any exceptions as we only pass objects of types the
|
||||
// serializer expects
|
||||
@SuppressWarnings("unchecked")
|
||||
final var serializer = (TypeSerializer<Object, ?>) TypeSerializer.newSerializerFor(
|
||||
element.getClass(),
|
||||
context.properties()
|
||||
);
|
||||
final var serialization = serializer.serialize(element);
|
||||
|
||||
requireSerializationNotContainsProperty(serialization);
|
||||
|
||||
final var result = new LinkedHashMap<>();
|
||||
result.put(annotation.property(), element.getClass().getName());
|
||||
result.putAll(serialization);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void requireSerializationNotContainsProperty(Map<?, ?> serialization) {
|
||||
if (serialization.containsKey(annotation.property())) {
|
||||
String msg = ("Polymorphic serialization for type '%s' failed. The type contains a " +
|
||||
"configuration element with name '%s' but that name is " +
|
||||
"used by the @Polymorphic property.")
|
||||
.formatted(polymorphicType.getName(), annotation.property());
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object deserialize(Map<?, ?> element) {
|
||||
requirePropertyPresent(element);
|
||||
|
||||
final var typeIdentifier = element.get(annotation.property());
|
||||
requireTypeIdentifierString(typeIdentifier);
|
||||
|
||||
Class<?> type = tryFindClass((String) typeIdentifier);
|
||||
TypeSerializer<?, ?> serializer = TypeSerializer.newSerializerFor(type, context.properties());
|
||||
return serializer.deserialize(element);
|
||||
}
|
||||
|
||||
private Class<?> tryFindClass(String className) {
|
||||
try {
|
||||
return Reflect.getClassByName(className);
|
||||
} catch (RuntimeException e) {
|
||||
String msg = ("Polymorphic deserialization for type '%s' failed. " +
|
||||
"The class '%s' does not exist.")
|
||||
.formatted(polymorphicType.getName(), className);
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void requirePropertyPresent(Map<?, ?> element) {
|
||||
if (element.get(annotation.property()) != null)
|
||||
return;
|
||||
String msg = """
|
||||
Polymorphic deserialization for type '%s' failed. \
|
||||
The property '%s' which holds the type is missing. \
|
||||
Value to be deserialized:
|
||||
%s\
|
||||
"""
|
||||
.formatted(
|
||||
polymorphicType.getName(),
|
||||
annotation.property(),
|
||||
element
|
||||
);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
private void requireTypeIdentifierString(Object typeIdentifier) {
|
||||
if (typeIdentifier instanceof String)
|
||||
return;
|
||||
String msg = ("Polymorphic deserialization for type '%s' failed. The type identifier '%s' " +
|
||||
"which should hold the type is not a string but of type '%s'.")
|
||||
.formatted(
|
||||
polymorphicType.getName(),
|
||||
typeIdentifier,
|
||||
typeIdentifier.getClass().getName()
|
||||
);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
Class<?> getPolymorphicType() {
|
||||
return polymorphicType;
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Serializers.ListSerializer;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.Polymorphic.DEFAULT_PROPERTY;
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class PolymorphicSerializerTest {
|
||||
private static final ConfigurationProperties PROPERTIES = ConfigurationProperties.newBuilder().build();
|
||||
private static final SerializerSelector SELECTOR = new SerializerSelector(PROPERTIES);
|
||||
|
||||
@Test
|
||||
void defaultPropertyNameIsType() {
|
||||
assertThat(DEFAULT_PROPERTY, is("type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeDoesNotAllowConfigurationElementWithSameNameAsProperty() {
|
||||
@Polymorphic
|
||||
@Configuration
|
||||
class A {
|
||||
String type = "";
|
||||
}
|
||||
@Configuration
|
||||
@Polymorphic(property = "prop")
|
||||
class B {
|
||||
String prop = "";
|
||||
}
|
||||
|
||||
record Config(A a, B b, List<A> as) {}
|
||||
|
||||
var serializerA = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
var serializerB = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "b"));
|
||||
@SuppressWarnings("unchecked")
|
||||
var serializerAs = (ListSerializer<A, Object>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
|
||||
String msg = "Polymorphic serialization for type '%s' failed. " +
|
||||
"The type contains a configuration element with name '%s' but that name is " +
|
||||
"used by the @Polymorphic property.";
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializerA.serialize(new A()),
|
||||
msg.formatted(A.class.getName(), "type")
|
||||
);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializerB.serialize(new B()),
|
||||
msg.formatted(B.class.getName(), "prop")
|
||||
);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializerAs.serialize(List.of(new A())),
|
||||
msg.formatted(A.class.getName(), "type")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeMissingTypeFails() {
|
||||
@Polymorphic
|
||||
interface A {}
|
||||
record R() implements A {}
|
||||
record Config(A a) {}
|
||||
|
||||
var invalidClassName = R.class.getName() + "_INVALID";
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(Map.of(DEFAULT_PROPERTY, invalidClassName)),
|
||||
("Polymorphic deserialization for type '%s' failed. " +
|
||||
"The class '%s' does not exist.")
|
||||
.formatted(A.class.getName(), invalidClassName)
|
||||
);
|
||||
}
|
||||
|
||||
static final class PolymorphicSerializerPropertyTest {
|
||||
private static final String CUSTOM_PROPERTY = DEFAULT_PROPERTY + DEFAULT_PROPERTY;
|
||||
|
||||
@Polymorphic(property = CUSTOM_PROPERTY)
|
||||
interface A {}
|
||||
|
||||
@Polymorphic(property = "")
|
||||
interface B {}
|
||||
|
||||
record R(int i) implements A {}
|
||||
|
||||
record S(int i) implements B {}
|
||||
|
||||
record Config(A a, B b, List<A> as, List<B> bs) {}
|
||||
|
||||
@Test
|
||||
void requirePropertyNameNonBlank() {
|
||||
RuntimeException exception = Assertions.assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> SELECTOR.select(fieldAsElement(Config.class, "b"))
|
||||
);
|
||||
ConfigurationException configurationException = (ConfigurationException)
|
||||
exception.getCause().getCause();
|
||||
Assertions.assertEquals(
|
||||
"The @Polymorphic annotation does not allow a blank property name but " +
|
||||
"type '%s' uses one.".formatted(B.class.getName()),
|
||||
configurationException.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requirePropertyNameNonBlankNested() {
|
||||
RuntimeException exception = Assertions.assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> SELECTOR.select(fieldAsElement(Config.class, "bs"))
|
||||
);
|
||||
ConfigurationException configurationException = (ConfigurationException)
|
||||
exception.getCause().getCause();
|
||||
Assertions.assertEquals(
|
||||
"The @Polymorphic annotation does not allow a blank property name but " +
|
||||
"type '%s' uses one.".formatted(B.class.getName()),
|
||||
configurationException.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeUsesPropertyName() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
Map<?, ?> serialize = serializer.serialize(new R(10));
|
||||
assertThat(serialize, is(Map.of(CUSTOM_PROPERTY, R.class.getName(), "i", 10L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeUsesPropertyName() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
R deserialize = (R) serializer.deserialize(Map.of(
|
||||
CUSTOM_PROPERTY, R.class.getName(),
|
||||
"i", 20L
|
||||
));
|
||||
assertThat(deserialize, is(new R(20)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeThrowsExceptionIfPropertyMissing() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
var serialized = Map.of("i", 20L);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(serialized),
|
||||
("Polymorphic deserialization for type '%s' failed. The property '%s' which " +
|
||||
"holds the type is missing. Value to be deserialized:\n%s")
|
||||
.formatted(A.class.getName(), CUSTOM_PROPERTY, serialized)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeThrowsExceptionIfPropertyMissingNested() {
|
||||
@SuppressWarnings("unchecked")
|
||||
var serializer = (ListSerializer<?, Map<?, ?>>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
var serialized = Map.of("i", 20L);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(List.of(serialized)),
|
||||
("Polymorphic deserialization for type '%s' failed. The property '%s' which " +
|
||||
"holds the type is missing. Value to be deserialized:\n%s")
|
||||
.formatted(A.class.getName(), CUSTOM_PROPERTY, serialized)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeThrowsExceptionIfPropertyHasWrongType() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a"));
|
||||
var serialized = Map.of(CUSTOM_PROPERTY, 1, "i", 20L);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(serialized),
|
||||
("Polymorphic deserialization for type '%s' failed. The type identifier '1' " +
|
||||
"which should hold the type is not a string but of type 'java.lang.Integer'.")
|
||||
.formatted(A.class.getName())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeThrowsExceptionIfPropertyHasWrongTypeNested() {
|
||||
@SuppressWarnings("unchecked")
|
||||
var serializer = (ListSerializer<?, Map<?, ?>>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
var serialized = Map.of(CUSTOM_PROPERTY, 1, "i", 20L);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(List.of(serialized)),
|
||||
("Polymorphic deserialization for type '%s' failed. The type identifier '1' " +
|
||||
"which should hold the type is not a string but of type 'java.lang.Integer'.")
|
||||
.formatted(A.class.getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class PolymorphicInterfaceSerializerTest {
|
||||
@Polymorphic
|
||||
interface A {}
|
||||
|
||||
@Configuration
|
||||
static final class Impl1 implements A {
|
||||
int i = 10;
|
||||
}
|
||||
|
||||
record Impl2(double d) implements A {}
|
||||
|
||||
static final class Config {
|
||||
A a1 = new Impl1();
|
||||
A a2 = new Impl2(20d);
|
||||
List<A> as = List.of(a1, a2);
|
||||
}
|
||||
|
||||
static final Config CONFIG = new Config();
|
||||
|
||||
@Test
|
||||
void getPolymorphicType() {
|
||||
var serializer1 = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
var serializer2 = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
assertThat(serializer1.getPolymorphicType(), equalTo(A.class));
|
||||
assertThat(serializer2.getPolymorphicType(), equalTo(A.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeA1() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
Map<?, ?> serialize = serializer.serialize(CONFIG.a1);
|
||||
assertThat(serialize, is(asMap(
|
||||
DEFAULT_PROPERTY, Impl1.class.getName(),
|
||||
"i", 10L
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeA1() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
Impl1 deserialize = (Impl1) serializer.deserialize(asMap(
|
||||
DEFAULT_PROPERTY, Impl1.class.getName(),
|
||||
"i", 20L
|
||||
));
|
||||
assertThat(deserialize.i, is(20));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeA2() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
Map<?, ?> serialize = serializer.serialize(CONFIG.a2);
|
||||
assertThat(serialize, is(asMap(
|
||||
DEFAULT_PROPERTY, Impl2.class.getName(),
|
||||
"d", 20d
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeA2() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
Impl2 deserialize = (Impl2) serializer.deserialize(asMap(
|
||||
DEFAULT_PROPERTY, Impl2.class.getName(),
|
||||
"d", 30d
|
||||
));
|
||||
assertThat(deserialize.d, is(30d));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeAs() {
|
||||
@SuppressWarnings("unchecked")
|
||||
var serializer = (ListSerializer<A, Object>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
List<?> serialize = serializer.serialize(CONFIG.as);
|
||||
assertThat(serialize, is(List.of(
|
||||
asMap(DEFAULT_PROPERTY, Impl1.class.getName(), "i", 10L),
|
||||
asMap(DEFAULT_PROPERTY, Impl2.class.getName(), "d", 20d)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeAs() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ListSerializer<A, Map<?, ?>> serializer = (ListSerializer<A, Map<?, ?>>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
List<A> deserialize = serializer.deserialize(List.of(
|
||||
asMap(DEFAULT_PROPERTY, Impl1.class.getName(), "i", 20L),
|
||||
asMap(DEFAULT_PROPERTY, Impl2.class.getName(), "d", 30d)
|
||||
));
|
||||
assertThat(deserialize.size(), is(2));
|
||||
|
||||
Impl1 actual1 = (Impl1) deserialize.get(0);
|
||||
Impl2 actual2 = (Impl2) deserialize.get(1);
|
||||
assertThat(actual1.i, is(20));
|
||||
assertThat(actual2, is(new Impl2(30)));
|
||||
}
|
||||
}
|
||||
|
||||
static final class PolymorphicAbstractClassSerializerTest {
|
||||
@Polymorphic
|
||||
@Configuration
|
||||
static abstract class A {
|
||||
String s1 = "s1";
|
||||
}
|
||||
|
||||
static class B extends A {
|
||||
String s2 = "s2";
|
||||
}
|
||||
|
||||
static final class Impl1 extends A {
|
||||
int i = 10;
|
||||
}
|
||||
|
||||
static final class Impl2 extends B {
|
||||
double d = 20d;
|
||||
}
|
||||
|
||||
static final class Config {
|
||||
A a1 = new Impl1();
|
||||
A a2 = new Impl2();
|
||||
List<A> as = List.of(a1, a2);
|
||||
}
|
||||
|
||||
static final Config CONFIG = new Config();
|
||||
|
||||
@Test
|
||||
void getPolymorphicType() {
|
||||
var serializer1 = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
var serializer2 = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
assertThat(serializer1.getPolymorphicType(), equalTo(A.class));
|
||||
assertThat(serializer2.getPolymorphicType(), equalTo(A.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeA1() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
Map<?, ?> serialize = serializer.serialize(CONFIG.a1);
|
||||
assertThat(serialize, is(asMap(
|
||||
DEFAULT_PROPERTY, Impl1.class.getName(),
|
||||
"s1", "s1",
|
||||
"i", 10L
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeA1() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a1"));
|
||||
Impl1 deserialize = (Impl1) serializer.deserialize(asMap(
|
||||
DEFAULT_PROPERTY, Impl1.class.getName(),
|
||||
"s1", "sa",
|
||||
"i", 20L
|
||||
));
|
||||
assertThat(deserialize.i, is(20));
|
||||
assertThat(deserialize.s1, is("sa"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeA2() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
Map<?, ?> serialize = serializer.serialize(CONFIG.a2);
|
||||
assertThat(serialize, is(asMap(
|
||||
DEFAULT_PROPERTY, Impl2.class.getName(),
|
||||
"s1", "s1",
|
||||
"s2", "s2",
|
||||
"d", 20d
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeA2() {
|
||||
var serializer = (PolymorphicSerializer) SELECTOR.select(fieldAsElement(Config.class, "a2"));
|
||||
Impl2 deserialize = (Impl2) serializer.deserialize(asMap(
|
||||
DEFAULT_PROPERTY, Impl2.class.getName(),
|
||||
"s1", "sa",
|
||||
"s2", "sb",
|
||||
"d", 30d
|
||||
));
|
||||
assertThat(deserialize.d, is(30d));
|
||||
assertThat(deserialize.s1, is("sa"));
|
||||
assertThat(deserialize.s2, is("sb"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeAs() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ListSerializer<A, ?> serializer = (ListSerializer<A, Object>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
List<?> serialize = serializer.serialize(CONFIG.as);
|
||||
assertThat(serialize, is(List.of(
|
||||
asMap(DEFAULT_PROPERTY, Impl1.class.getName(), "s1", "s1", "i", 10L),
|
||||
asMap(DEFAULT_PROPERTY, Impl2.class.getName(), "s1", "s1", "s2", "s2", "d", 20d)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeAs() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ListSerializer<A, Map<?, ?>> serializer = (ListSerializer<A, Map<?, ?>>)
|
||||
SELECTOR.select(fieldAsElement(Config.class, "as"));
|
||||
List<A> deserialize = serializer.deserialize(List.of(
|
||||
asMap(DEFAULT_PROPERTY, Impl1.class.getName(), "s1", "sa", "i", 20L),
|
||||
asMap(DEFAULT_PROPERTY, Impl2.class.getName(), "s1", "sa", "s2", "sb", "d", 30d)
|
||||
));
|
||||
assertThat(deserialize.size(), is(2));
|
||||
|
||||
Impl1 actual1 = (Impl1) deserialize.get(0);
|
||||
assertThat(actual1.i, is(20));
|
||||
assertThat(actual1.s1, is("sa"));
|
||||
|
||||
Impl2 actual2 = (Impl2) deserialize.get(1);
|
||||
assertThat(actual2.d, is(30d));
|
||||
assertThat(actual2.s1, is("sa"));
|
||||
assertThat(actual2.s2, is("sb"));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue