Search for custom serializers/deserializers/adapters in a deterministic way
diff --git a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
index 57948a4..c82ffbd 100644
--- a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
+++ b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
@@ -137,7 +137,6 @@
* @param customization with component info
* @return serializer optional
*/
- @SuppressWarnings("unchecked")
public Optional<SerializerBinding<?>> getSerializerBinding(Type propertyRuntimeType,
ComponentBoundCustomization customization) {
@@ -154,7 +153,6 @@
* @param customization customization with component info
* @return serializer optional
*/
- @SuppressWarnings("unchecked")
public Optional<DeserializerBinding<?>> getDeserializerBinding(Type propertyRuntimeType,
ComponentBoundCustomization customization) {
if (customization == null || customization.getDeserializerBinding() == null) {
@@ -196,12 +194,46 @@
}
private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(Type runtimeType, Function<ComponentBindings, T> supplier) {
- for (ComponentBindings componentBindings : userComponents.values()) {
- final T component = supplier.apply(componentBindings);
- if (component != null && matches(runtimeType, componentBindings.getBindingType())) {
- return Optional.of(component);
+ // First check if there is an exact match
+ ComponentBindings binding = userComponents.get(runtimeType);
+ if (binding != null) {
+ Optional<T> match = getMatchingBinding(runtimeType, binding, supplier);
+ if (match.isPresent()) {
+ return match;
}
}
+
+ if (runtimeType instanceof Class) {
+ Class<?> runtimeClass = (Class<?>) runtimeType;
+ // Check if any interfaces have a match
+ for (Class<?> ifc : runtimeClass.getInterfaces()) {
+ ComponentBindings ifcBinding = userComponents.get(ifc);
+ if (ifcBinding != null) {
+ Optional<T> match = getMatchingBinding(ifc, ifcBinding, supplier);
+ if (match.isPresent()) {
+ return match;
+ }
+ }
+ }
+
+ // check if the superclass has a match
+ Class<?> superClass = runtimeClass.getSuperclass();
+ if (superClass != null && superClass != Object.class) {
+ Optional<T> superBinding = searchComponentBinding(superClass, supplier);
+ if (superBinding.isPresent()) {
+ return superBinding;
+ }
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ private <T> Optional<T> getMatchingBinding(Type runtimeType, ComponentBindings binding, Function<ComponentBindings, T> supplier) {
+ final T component = supplier.apply(binding);
+ if (component != null && matches(runtimeType, binding.getBindingType())) {
+ return Optional.of(component);
+ }
return Optional.empty();
}
diff --git a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
index 8336d2f..506497f 100644
--- a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
+++ b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
@@ -12,11 +12,13 @@
package org.eclipse.yasson.serializers;
-import org.junit.jupiter.api.*;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.eclipse.yasson.Jsonbs.*;
-
import static java.util.Collections.singletonMap;
+import static org.eclipse.yasson.Jsonbs.defaultJsonb;
+import static org.eclipse.yasson.Jsonbs.nullableJsonb;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.io.StringReader;
import java.lang.reflect.Type;
@@ -64,6 +66,7 @@
import org.eclipse.yasson.serializers.model.SimpleContainer;
import org.eclipse.yasson.serializers.model.StringWrapper;
import org.eclipse.yasson.serializers.model.SupertypeSerializerPojo;
+import org.junit.jupiter.api.Test;
/**
* @author Roman Grigoriadi
@@ -514,4 +517,81 @@
crateInner.crateInnerBigDec = BigDecimal.TEN;
return crateInner;
}
+
+ public static class Foo { }
+
+ public static class Bar extends Foo { }
+
+ public static class Baz extends Bar { }
+
+ public static class FooSerializer implements JsonbSerializer<Foo> {
+ public void serialize(Foo obj, JsonGenerator generator, SerializationContext ctx) {
+ generator.write("foo");
+ }
+ }
+
+ public static class BazSerializer implements JsonbSerializer<Baz> {
+ public void serialize(Baz obj, JsonGenerator generator, SerializationContext ctx) {
+ generator.write("baz");
+ }
+ }
+
+ /**
+ * Test for issue: https://github.com/quarkusio/quarkus/issues/8925
+ * Ensure that if multiple customizations (serializer, deserializer, or adapter) are applied
+ * for different types in the same class hierarchy, we use the customization for the most
+ * specific type in the class hierarchy.
+ */
+ @Test
+ public void testSerializerMatching() {
+ Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withSerializers(new FooSerializer(), new BazSerializer()));
+ assertEquals("\"foo\"", jsonb.toJson(new Foo()));
+ // Since 'Bar' does not have its own serializer, it should use
+ // the next serializer in the tree (FooSerializer)
+ assertEquals("\"foo\"", jsonb.toJson(new Bar()));
+ assertEquals("\"baz\"", jsonb.toJson(new Baz()));
+ }
+
+ public static interface One { }
+ public static interface Two { }
+ public static interface Three { }
+
+ public static class OneTwo implements One, Two { }
+ public static class OneTwoThree implements One, Two, Three { }
+
+ public static class OneSerializer implements JsonbSerializer<One> {
+ public void serialize(One obj, JsonGenerator generator, SerializationContext ctx) {
+ generator.write("one");
+ }
+ }
+
+ public static class TwoSerializer implements JsonbSerializer<Two> {
+ public void serialize(Two obj, JsonGenerator generator, SerializationContext ctx) {
+ generator.write("two");
+ }
+ }
+
+ public static class ThreeSerializer implements JsonbSerializer<Three> {
+ public void serialize(Three obj, JsonGenerator generator, SerializationContext ctx) {
+ generator.write("three");
+ }
+ }
+
+ @Test
+ public void testSerializerMatchingInterfaces01() {
+ Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withSerializers(new OneSerializer(), new TwoSerializer(), new ThreeSerializer()));
+ assertEquals("\"one\"", jsonb.toJson(new OneTwo()));
+ assertEquals("\"one\"", jsonb.toJson(new OneTwoThree()));
+ }
+
+ @Test
+ public void testSerializerMatchingInterfaces02() {
+ Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withSerializers(new ThreeSerializer(), new TwoSerializer()));
+ assertEquals("\"two\"", jsonb.toJson(new OneTwo()));
+ assertEquals("\"two\"", jsonb.toJson(new OneTwoThree()));
+ }
+
}