diff --git a/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordInstantiator.java b/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordInstantiator.java new file mode 100644 index 00000000..20345c40 --- /dev/null +++ b/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordInstantiator.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.module.androidrecord; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.CreatorProperty; +import com.fasterxml.jackson.databind.deser.SettableBeanProperty; +import com.fasterxml.jackson.databind.deser.ValueInstantiator; +import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator; +import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +public class AndroidRecordInstantiator extends StdValueInstantiator { + protected AndroidRecordInstantiator(StdValueInstantiator src) { + super(src); + } + + public ValueInstantiator createContextual(DeserializationContext ctxt, BeanDescription beanDesc) throws JsonMappingException { + boolean wasChanged = false; + SettableBeanProperty[] newCtorArgs = new SettableBeanProperty[_constructorArguments.length]; + for (int i = 0; i < _constructorArguments.length; i++) { + SettableBeanProperty prop = _constructorArguments[i]; + if (!prop.hasValueTypeDeserializer()) { + TypeDeserializer typeDeserializer = ctxt.getFactory().findTypeDeserializer(ctxt.getConfig(), prop.getType()); + if (typeDeserializer != null) { + prop = CreatorProperty.construct( + prop.getFullName(), + prop.getType(), + prop.getWrapperName(), + typeDeserializer, + prop.getMember().getAllAnnotations(), + (AnnotatedParameter) prop.getMember(), + prop.getCreatorIndex(), + ctxt.getAnnotationIntrospector().findInjectableValue(prop.getMember()), + prop.getMetadata() + ); + wasChanged = true; + } + } + newCtorArgs[i] = prop; + } + + + if (wasChanged) { + AndroidRecordInstantiator newInstantiator = new AndroidRecordInstantiator(this); + newInstantiator._constructorArguments = newCtorArgs; + return newInstantiator; + } else { + return this; + } + } +} diff --git a/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java b/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java index 2f5b9973..9abd5172 100644 --- a/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java +++ b/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java @@ -153,10 +153,13 @@ && isDesugaredRecordClass(raw)) { null, null, parameter.getAllAnnotations(), parameter, i, injectable, null); } - ((StdValueInstantiator) defaultInstantiator).configureFromObjectSettings(null, null, null, null, - constructor, properties); + AndroidRecordInstantiator instantiator = new AndroidRecordInstantiator((StdValueInstantiator) defaultInstantiator); + instantiator.configureFromObjectSettings(null, null, null, null, constructor, properties); + ClassUtil.checkAndFixAccess(constructor.getAnnotated(), false); found = true; + + defaultInstantiator = instantiator; } } diff --git a/android-record/src/test/java/com/fasterxml/jackson/module/androidrecord/AbstractRecordMemberTest.java b/android-record/src/test/java/com/fasterxml/jackson/module/androidrecord/AbstractRecordMemberTest.java new file mode 100644 index 00000000..387f8615 --- /dev/null +++ b/android-record/src/test/java/com/fasterxml/jackson/module/androidrecord/AbstractRecordMemberTest.java @@ -0,0 +1,75 @@ +package com.fasterxml.jackson.module.androidrecord; + +import com.android.tools.r8.RecordTag; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class AbstractRecordMemberTest extends BaseMapTest { + + static final class RootRecord extends RecordTag { + + private final AbstractMember member; + + public RootRecord(AbstractMember member) { + this.member = member; + } + + public AbstractMember member() { + return member; + } + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@class") + @JsonSubTypes({ + @JsonSubTypes.Type(value = StringMember.class, name = "string"), + @JsonSubTypes.Type(value = IntMember.class, name = "int") + }) + static abstract class AbstractMember { + } + + static final class StringMember extends AbstractMember { + + private final String val; + + @JsonCreator + public StringMember(@JsonProperty("val") String val) { + this.val = val; + } + + public String getVal() { + return val; + } + } + + static final class IntMember extends AbstractMember { + + private final int val; + + @JsonCreator + public IntMember(@JsonProperty("val") int val) { + this.val = val; + } + + public int getVal() { + return val; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* https://github.com/FasterXML/jackson-modules-base/issues/248 + /********************************************************************** + */ + public void testDeserializeRecordWithAbstractMember() throws Exception { + RootRecord value = MAPPER.readValue("{\"member\":{\"@class\":\"string\",\"val\":\"Hello, abstract member!\"}}", + RootRecord.class); + assertNotNull(value.member()); + assertEquals(StringMember.class, value.member().getClass()); + assertEquals("Hello, abstract member!", ((StringMember)value.member()).getVal()); + } +}