Skip to content

Commit

Permalink
Search for custom serializers/deserializers/adapters in a determinist…
Browse files Browse the repository at this point in the history
…ic way
  • Loading branch information
aguibert committed May 14, 2020
1 parent a899d73 commit e7bbf64
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 21 deletions.
44 changes: 38 additions & 6 deletions src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ private void registerGeneric(Type bindingType) {
* @param customization with component info
* @return serializer optional
*/
@SuppressWarnings("unchecked")
public Optional<SerializerBinding<?>> getSerializerBinding(Type propertyRuntimeType,
ComponentBoundCustomization customization) {

Expand All @@ -154,7 +153,6 @@ public Optional<SerializerBinding<?>> getSerializerBinding(Type propertyRuntimeT
* @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) {
Expand Down Expand Up @@ -196,11 +194,45 @@ public Optional<AdapterBinding> getDeserializeAdapterBinding(Type propertyRuntim
}

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();
}
Expand Down
113 changes: 98 additions & 15 deletions src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,17 +32,6 @@
import java.util.TimeZone;
import java.util.TreeMap;

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.JsonbException;
import jakarta.json.bind.config.PropertyOrderStrategy;
import jakarta.json.bind.serializer.DeserializationContext;
import jakarta.json.bind.serializer.JsonbDeserializer;
import jakarta.json.stream.JsonParser;

import org.eclipse.yasson.TestTypeToken;
import org.eclipse.yasson.internal.model.ReverseTreeMap;
import org.eclipse.yasson.serializers.model.AnnotatedGenericWithSerializerType;
Expand All @@ -64,6 +55,21 @@
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;

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.JsonbException;
import jakarta.json.bind.config.PropertyOrderStrategy;
import jakarta.json.bind.serializer.DeserializationContext;
import jakarta.json.bind.serializer.JsonbDeserializer;
import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;

/**
* @author Roman Grigoriadi
Expand Down Expand Up @@ -514,4 +520,81 @@ private static CrateInner createCrateInner(String name) {
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()));
}

}

0 comments on commit e7bbf64

Please sign in to comment.