Merge pull request #686 from jamezp/issue685
[685] Ensure the value type of a container being deserialized uses th…
diff --git a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
index 1f1bdab..6a6daca 100644
--- a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
+++ b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
@@ -180,15 +180,15 @@
return typeDeserializer;
}
if (Collection.class.isAssignableFrom(rawType)) {
- return createCollectionDeserializer(cachedItem, rawType, chain, propertyCustomization);
+ return createCollectionDeserializer(cachedItem, rawType, chain);
} else if (Map.class.isAssignableFrom(rawType)) {
- return createMapDeserializer(cachedItem, rawType, chain, propertyCustomization);
+ return createMapDeserializer(cachedItem, rawType, chain);
} else if (rawType.isArray()) {
- return createArrayDeserializer(cachedItem, rawType, chain, propertyCustomization);
+ return createArrayDeserializer(cachedItem, rawType, chain);
} else if (type instanceof GenericArrayType) {
- return createGenericArray(cachedItem, rawType, chain, propertyCustomization);
+ return createGenericArray(cachedItem, rawType, chain);
} else if (Optional.class.isAssignableFrom(rawType)) {
- return createOptionalDeserializer(chain, type, propertyCustomization, cachedItem);
+ return createOptionalDeserializer(chain, type, cachedItem);
} else {
return createObjectDeserializer(chain, type, propertyCustomization, classModel, rawType, cachedItem);
}
@@ -262,8 +262,7 @@
private ModelDeserializer<JsonParser> createCollectionDeserializer(CachedItem cachedItem,
Class<?> rawType,
- LinkedList<Type> chain,
- Customization propertyCustomization) {
+ LinkedList<Type> chain) {
Type type = cachedItem.type;
Type colType = type instanceof ParameterizedType
? ((ParameterizedType) type).getActualTypeArguments()[0]
@@ -284,8 +283,7 @@
private ModelDeserializer<JsonParser> createMapDeserializer(CachedItem cachedItem,
Class<?> rawType,
- LinkedList<Type> chain,
- Customization propertyCustomization) {
+ LinkedList<Type> chain) {
Type type = cachedItem.type;
Type keyType = type instanceof ParameterizedType
? ((ParameterizedType) type).getActualTypeArguments()[0]
@@ -298,9 +296,10 @@
ClassCustomization.empty(),
JustReturn.instance(),
MAP_KEY_EVENTS);
+ ClassModel valueClassModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.resolveRawType(chain, valueType));
ModelDeserializer<JsonParser> valueProcessor = typeProcessor(chain,
valueType,
- propertyCustomization,
+ valueClassModel.getClassCustomization(),
JustReturn.instance());
MapDeserializer mapDeserializer = new MapDeserializer(keyProcessor, valueProcessor);
@@ -315,14 +314,15 @@
private ModelDeserializer<JsonParser> createArrayDeserializer(CachedItem cachedItem,
Class<?> rawType,
- LinkedList<Type> chain,
- Customization propertyCustomization) {
+ LinkedList<Type> chain) {
JsonbConfigProperties configProperties = jsonbContext.getConfigProperties();
if (rawType.equals(byte[].class) && !configProperties.getBinaryDataStrategy().equals(BinaryDataStrategy.BYTE)) {
String strategy = configProperties.getBinaryDataStrategy();
+ // Special case for byte[] with base64 encoding - use String's class customization
+ ClassModel stringModel = jsonbContext.getMappingContext().getOrCreateClassModel(String.class);
ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
String.class,
- propertyCustomization,
+ stringModel.getClassCustomization(),
JustReturn.instance());
ModelDeserializer<JsonParser> base64Deserializer = ArrayInstanceCreator.createBase64Deserializer(strategy,
typeProcessor);
@@ -331,22 +331,23 @@
return nullChecker;
}
Class<?> arrayType = rawType.getComponentType();
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(arrayType);
ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
arrayType,
- propertyCustomization,
+ classModel.getClassCustomization(),
JustReturn.instance());
return createArrayCommonDeserializer(cachedItem, rawType, arrayType, typeProcessor);
}
private ModelDeserializer<JsonParser> createGenericArray(CachedItem cachedItem,
Class<?> rawType,
- LinkedList<Type> chain,
- Customization propertyCustomization) {
+ LinkedList<Type> chain) {
GenericArrayType type = (GenericArrayType) cachedItem.type;
Class<?> component = ReflectionUtils.getRawType(type.getGenericComponentType());
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(component);
ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
type.getGenericComponentType(),
- propertyCustomization,
+ classModel.getClassCustomization(),
JustReturn.instance());
return createArrayCommonDeserializer(cachedItem, rawType, component, typeProcessor);
}
@@ -365,12 +366,13 @@
private OptionalDeserializer createOptionalDeserializer(LinkedList<Type> chain,
Type type,
- Customization propertyCustomization,
CachedItem cachedItem) {
Type colType = type instanceof ParameterizedType
? ((ParameterizedType) type).getActualTypeArguments()[0]
: Object.class;
- ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain, colType, propertyCustomization, JustReturn.instance());
+ colType = ReflectionUtils.resolveType(chain, colType);
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(colType));
+ ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain, colType, classModel.getClassCustomization(), JustReturn.instance());
OptionalDeserializer optionalDeserializer = new OptionalDeserializer(typeProcessor, JustReturn.instance());
models.put(cachedItem, optionalDeserializer);
return optionalDeserializer;
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java b/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
index 0379c55..9aa29a7 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
@@ -302,7 +302,9 @@
Type resolvedKey = ReflectionUtils.resolveType(chain, keyType);
Class<?> rawClass = ReflectionUtils.getRawType(resolvedKey);
ModelSerializer keySerializer = memberSerializer(chain, keyType, ClassCustomization.empty(), true);
- ModelSerializer valueSerializer = memberSerializer(chain, valueType, propertyCustomization, false);
+ Type resolvedValue = ReflectionUtils.resolveType(chain, valueType);
+ ClassModel valueClassModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(resolvedValue));
+ ModelSerializer valueSerializer = memberSerializer(chain, valueType, valueClassModel.getClassCustomization(), false);
MapSerializer mapSerializer = MapSerializer.create(rawClass, keySerializer, valueSerializer, jsonbContext);
KeyWriter keyWriter = new KeyWriter(mapSerializer);
NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
@@ -313,7 +315,8 @@
Class<?> raw,
Customization propertyCustomization) {
Class<?> arrayComponent = raw.getComponentType();
- ModelSerializer modelSerializer = memberSerializer(chain, arrayComponent, propertyCustomization, false);
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(arrayComponent);
+ ModelSerializer modelSerializer = memberSerializer(chain, arrayComponent, classModel.getClassCustomization(), false);
ModelSerializer arraySerializer = ArraySerializer.create(raw, jsonbContext, modelSerializer);
KeyWriter keyWriter = new KeyWriter(arraySerializer);
NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
@@ -325,7 +328,8 @@
Customization propertyCustomization) {
Class<?> raw = ReflectionUtils.getRawType(type);
Class<?> component = ReflectionUtils.getRawType(((GenericArrayType) type).getGenericComponentType());
- ModelSerializer modelSerializer = memberSerializer(chain, component, propertyCustomization, false);
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(component);
+ ModelSerializer modelSerializer = memberSerializer(chain, component, classModel.getClassCustomization(), false);
ModelSerializer arraySerializer = ArraySerializer.create(raw, jsonbContext, modelSerializer);
KeyWriter keyWriter = new KeyWriter(arraySerializer);
NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
@@ -339,7 +343,9 @@
Type optType = type instanceof ParameterizedType
? ((ParameterizedType) type).getActualTypeArguments()[0]
: Object.class;
- ModelSerializer modelSerializer = memberSerializer(chain, optType, propertyCustomization, isKey);
+ Type resolvedOptType = ReflectionUtils.resolveType(chain, optType);
+ ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(resolvedOptType));
+ ModelSerializer modelSerializer = memberSerializer(chain, optType, classModel.getClassCustomization(), isKey);
return new OptionalSerializer(modelSerializer);
}
diff --git a/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java b/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java
new file mode 100644
index 0000000..7cd1e3f
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2025 IBM and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.serializers;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import jakarta.json.bind.annotation.JsonbTypeDeserializer;
+import jakarta.json.bind.config.BinaryDataStrategy;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.stream.JsonParser;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link jakarta.json.bind.annotation.JsonbTypeDeserializer @JsonbTypeDeserializer} annotated types are
+ * properly detected and used when those types are used as elements/values in containers (Maps, Collections,
+ * Arrays, Optionals).
+ *
+ * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a>
+ */
+public class TypeDeserializerOnContainersTest {
+
+ // Test interface with type-level deserializer annotation
+ @JsonbTypeDeserializer(TestInterfaceDeserializer.class)
+ public interface TestInterface {
+ String getValue();
+ }
+
+ // Implementation of the test interface
+ public static class TestImpl implements TestInterface {
+ private final String value;
+
+ public TestImpl(final String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ }
+
+ // Custom deserializer for TestInterface
+ public static class TestInterfaceDeserializer implements JsonbDeserializer<TestInterface> {
+ @Override
+ public TestInterface deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) {
+ // Parse the JSON object to get the value field
+ Assertions.assertTrue(parser.hasNext(), "Expected the key name");
+ parser.next();
+ Assertions.assertTrue(parser.hasNext(), "Expected the value");
+ parser.next();
+ final String value = parser.getString();
+ Assertions.assertTrue(parser.hasNext(), "Expected the end of an object");
+ parser.next();
+ return new TestImpl("DESERIALIZED:" + value);
+ }
+ }
+
+ // Container classes for testing
+ public static class MapContainer {
+ public Map<String, TestInterface> map;
+ public Map<?, ?> questionKeyMap;
+ public Map<String, ?> questionValueMap;
+ }
+
+ public static class ListContainer {
+ public List<TestInterface> list;
+ public List<?> questionList;
+ }
+
+ public static class ArrayContainer {
+ public TestInterface[] array;
+ }
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ public static class OptionalContainer {
+ public Optional<TestInterface> optional;
+ public Optional<?> questionOptional;
+ }
+
+ public static class ByteArrayContainer {
+ public byte[] data;
+ }
+
+ private Jsonb jsonb;
+
+ @BeforeEach
+ public void createJsonb() {
+ // Create a new Jsonb for each test to avoid type caching
+ jsonb = JsonbBuilder.create();
+ }
+
+ @AfterEach
+ public void closeJsonb() throws Exception {
+ if (jsonb != null) {
+ jsonb.close();
+ }
+ }
+
+ @Test
+ public void testTypeDeserializerOnMapValues() {
+ final String json = "{\"map\":{\"key1\":{\"value\":\"value1\"},\"key2\":{\"value\":\"value2\"}}, \"questionKeyMap\":{\"qKey1\":\"value1\",\"qKey2\":\"value2\"},\"questionValueMap\":{\"key1\":\"qValue1\",\"key2\":\"qValue2\"}}";
+
+ final MapContainer result = jsonb.fromJson(json, MapContainer.class);
+
+ Assertions.assertNotNull(result.map);
+ Assertions.assertEquals(2, result.map.size(), () -> String.format("Expected two entries got %s", result.map));
+ Assertions.assertEquals("DESERIALIZED:value1", result.map.get("key1").getValue());
+ Assertions.assertEquals("DESERIALIZED:value2", result.map.get("key2").getValue());
+
+ Assertions.assertNotNull(result.questionKeyMap);
+ Assertions.assertEquals(2, result.questionKeyMap.size(), () -> String.format("Expected two entries got %s", result.questionKeyMap));
+ Assertions.assertEquals("value1", result.questionKeyMap.get("qKey1"));
+ Assertions.assertEquals("value2", result.questionKeyMap.get("qKey2"));
+
+ Assertions.assertNotNull(result.questionValueMap);
+ Assertions.assertEquals(2, result.questionValueMap.size(), () -> String.format("Expected two entries got %s", result.questionValueMap));
+ Assertions.assertEquals("qValue1", result.questionValueMap.get("key1"));
+ Assertions.assertEquals("qValue2", result.questionValueMap.get("key2"));
+ }
+
+ @Test
+ public void testTypeDeserializerOnListElements() {
+ final String json = "{\"list\":[{\"value\":\"value1\"},{\"value\":\"value2\"}], \"questionList\": [\"value1\", \"value2\"]}";
+
+ final ListContainer result = jsonb.fromJson(json, ListContainer.class);
+
+ Assertions.assertNotNull(result.list);
+ Assertions.assertEquals(2, result.list.size(), () -> String.format("Expected two entries got %s", result.list));
+ Assertions.assertEquals("DESERIALIZED:value1", result.list.get(0).getValue());
+ Assertions.assertEquals("DESERIALIZED:value2", result.list.get(1).getValue());
+
+ Assertions.assertNotNull(result.questionList);
+ Assertions.assertEquals(2, result.questionList.size(), () -> String.format("Expected two entries got %s", result.questionList));
+ Assertions.assertEquals("value1", result.questionList.get(0));
+ Assertions.assertEquals("value2", result.questionList.get(1));
+ }
+
+ @Test
+ public void testTypeDeserializerOnArrayElements() {
+ final String json = "{\"array\":[{\"value\":\"value1\"},{\"value\":\"value2\"}]}";
+
+ final ArrayContainer result = jsonb.fromJson(json, ArrayContainer.class);
+
+ Assertions.assertNotNull(result.array);
+ Assertions.assertEquals(2, result.array.length, () -> String.format("Expected two entries got %s", Arrays.toString(result.array)));
+ Assertions.assertEquals("DESERIALIZED:value1", result.array[0].getValue());
+ Assertions.assertEquals("DESERIALIZED:value2", result.array[1].getValue());
+ }
+
+ @Test
+ public void testTypeDeserializerOnOptionalValue() {
+ final String json = "{\"optional\":{\"value\":\"value1\"},\"questionOptional\":\"value2\"}";
+
+ final OptionalContainer result = jsonb.fromJson(json, OptionalContainer.class);
+
+ Assertions.assertNotNull(result.optional);
+ Assertions.assertTrue(result.optional.isPresent(), "Expected value to be present, but the optional was empty.");
+ Assertions.assertEquals("DESERIALIZED:value1", result.optional.get().getValue());
+
+ Assertions.assertNotNull(result.questionOptional);
+ Assertions.assertTrue(result.questionOptional.isPresent(), "Expected value to be present, but the optional was empty.");
+ Assertions.assertEquals("value2", result.questionOptional.get());
+ }
+
+ @Test
+ public void testTypeDeserializerOnByteArray() {
+ final String json = "{\"data\":[1,2,3,4,5]}";
+
+ final ByteArrayContainer result = jsonb.fromJson(json, ByteArrayContainer.class);
+
+ Assertions.assertNotNull(result.data);
+ Assertions.assertEquals(5, result.data.length);
+ Assertions.assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, result.data);
+ }
+
+ @Test
+ public void testTypeDeserializerOnByteArrayWithBase64() throws Exception {
+ try (Jsonb base64Jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withBinaryDataStrategy(BinaryDataStrategy.BASE_64))) {
+
+ // "SGVsbG8=" is "Hello" in base64
+ final String json = "{\"data\":\"SGVsbG8=\"}";
+
+ final ByteArrayContainer result = base64Jsonb.fromJson(json, ByteArrayContainer.class);
+
+ Assertions.assertNotNull(result.data);
+ Assertions.assertArrayEquals("Hello".getBytes(), result.data);
+ }
+ }
+}
diff --git a/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java b/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java
new file mode 100644
index 0000000..89f3ff2
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 IBM and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.serializers;
+
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbException;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link jakarta.json.bind.annotation.JsonbTypeSerializer @JsonbTypeSerializer} annotated types are
+ * properly detected and used when those types are used as elements/values in containers (Maps, Collections,
+ * Arrays, Optionals).
+ *
+ * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a>
+ */
+public class TypeSerializerOnContainersTest {
+
+ // Test interface with type-level serializer annotation
+ @JsonbTypeSerializer(TestInterfaceSerializer.class)
+ public interface TestInterface {
+ String getValue();
+ }
+
+ // Implementation of the test interface
+ public static class TestImpl implements TestInterface {
+ private final String value;
+
+ public TestImpl(final String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ }
+
+ // Custom serializer for TestInterface
+ public static class TestInterfaceSerializer implements JsonbSerializer<TestInterface> {
+ @Override
+ public void serialize(final TestInterface obj, final JsonGenerator generator, final SerializationContext ctx) {
+ generator.write("SERIALIZED:" + obj.getValue());
+ }
+ }
+
+ // Container classes for testing
+ public static class MapContainer {
+ public final Map<String, TestInterface> map;
+ public final Map<?, ?> questionKeyMap;
+ public final Map<String, ?> questionValueMap;
+
+ public MapContainer(final Map<String, TestInterface> map, final Map<?, ?> questionKeyMap, final Map<String, ?> questionValueMap) {
+ this.map = map;
+ this.questionKeyMap = questionKeyMap;
+ this.questionValueMap = questionValueMap;
+ }
+ }
+
+ public static class ListContainer {
+ public final List<TestInterface> list;
+ public final List<?> questionList;
+
+ public ListContainer(final List<TestInterface> list, final List<?> questionList) {
+ this.list = list;
+ this.questionList = questionList;
+ }
+ }
+
+ public static class ArrayContainer {
+ public final TestInterface[] array;
+
+ public ArrayContainer(TestInterface[] array) {
+ this.array = array;
+ }
+ }
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ public static class OptionalContainer {
+ public final Optional<TestInterface> optional;
+ public final Optional<?> questionOptional;
+
+ public OptionalContainer(final Optional<TestInterface> optional, final Optional<?> questionOptional) {
+ this.optional = optional;
+ this.questionOptional = questionOptional;
+ }
+ }
+
+ private Jsonb jsonb;
+
+ @BeforeEach
+ public void createJsonb() {
+ // Create a new Jsonb for each test to avoid type caching
+ jsonb = JsonbBuilder.create();
+ }
+
+ @AfterEach
+ public void closeJsonb() throws Exception {
+ if (jsonb != null) {
+ jsonb.close();
+ }
+ }
+
+ @Test
+ public void testTypeSerializerOnMapValues() {
+ final MapContainer container = new MapContainer(Map.of(
+ "key1", new TestImpl("value1"),
+ "key2", new TestImpl("value2")
+ ), Map.of("qKey1", "value1", "qKey2", "value2"),
+ Map.of("key1", "qValue1", "key2", "qValue2")
+ );
+
+ final JsonObject json = toJsonObject(container);
+ final JsonObject map = json.getJsonObject("map");
+ final JsonObject questionKeyMap = json.getJsonObject("questionKeyMap");
+ final JsonObject questionValueMap = json.getJsonObject("questionValueMap");
+
+ Supplier<String> errorMessage = () -> String.format("Expected value not found in %s", map);
+ Assertions.assertEquals("SERIALIZED:value1", map.getString("key1"), errorMessage);
+ Assertions.assertEquals("SERIALIZED:value2", map.getString("key2"), errorMessage);
+
+
+ errorMessage = () -> String.format("Expected value not found in %s", questionKeyMap);
+ Assertions.assertEquals("value1", questionKeyMap.getString("qKey1"), errorMessage);
+ Assertions.assertEquals("value2", questionKeyMap.getString("qKey2"), errorMessage);
+
+
+ errorMessage = () -> String.format("Expected value not found in %s", questionValueMap);
+ Assertions.assertEquals("qValue1", questionValueMap.getString("key1"), errorMessage);
+ Assertions.assertEquals("qValue2", questionValueMap.getString("key2"), errorMessage);
+ }
+
+ @Test
+ public void testTypeSerializerOnListElements() {
+ final ListContainer container = new ListContainer(List.of(
+ new TestImpl("value1"),
+ new TestImpl("value2")
+ ), List.of("qValue1", "qValue2"));
+
+ final JsonObject json = toJsonObject(container);
+ final JsonArray list = json.getJsonArray("list");
+ final JsonArray questionList = json.getJsonArray("questionList");
+
+ Supplier<String> errorMessage = () -> String.format("Expected value not found in %s", list);
+ Assertions.assertEquals(2, list.size(), () -> String.format("Expected a size of 2 in %s", list));
+ Assertions.assertEquals("SERIALIZED:value1", list.getString(0), errorMessage);
+ Assertions.assertEquals("SERIALIZED:value2", list.getString(1), errorMessage);
+
+ errorMessage = () -> String.format("Expected value not found in %s", questionList);
+ Assertions.assertEquals(2, questionList.size(), () -> String.format("Expected a size of 2 in %s", questionList));
+ Assertions.assertEquals("qValue1", questionList.getString(0), errorMessage);
+ Assertions.assertEquals("qValue2", questionList.getString(1), errorMessage);
+ }
+
+ @Test
+ public void testTypeSerializerOnArrayElements() {
+ final ArrayContainer container = new ArrayContainer(new TestInterface[] {
+ new TestImpl("value1"),
+ new TestImpl("value2")
+ });
+
+ final String json = jsonb.toJson(container);
+
+ Assertions.assertEquals("{\"array\":[\"SERIALIZED:value1\",\"SERIALIZED:value2\"]}", json);
+ }
+
+ @Test
+ public void testTypeSerializerOnOptionalValue() {
+ final OptionalContainer container = new OptionalContainer(Optional.of(new TestImpl("value1")), Optional.of("value2"));
+
+ final JsonObject json = toJsonObject(container);
+
+ Assertions.assertEquals("SERIALIZED:value1", json.getString("optional"));
+ Assertions.assertEquals("value2", json.getString("questionOptional"));
+ }
+
+ private JsonObject toJsonObject(final Object object) throws JsonbException {
+ final String value = jsonb.toJson(object);
+ try (
+ StringReader reader = new StringReader(value);
+ JsonReader jsonReader = Json.createReader(reader)
+ ) {
+ return jsonReader.readObject();
+ }
+ }
+}