jackson-modules-base icon indicating copy to clipboard operation
jackson-modules-base copied to clipboard

jackson-module-android-record: Class annotations and polymorphic types are ignored when deserializing record fields

Open HelloOO7 opened this issue 1 year ago • 4 comments

Suppose a class hierarchy like this:

public record Record(Field field) {
}

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="@class")
@JsonSubTypes({
        @JsonSubTypes.Type(value = StringField.class, name = "string"),
        @JsonSubTypes.Type(value = IntField.class, name = "int")
})
public abstract class Field {
}

public class StringField extends Field {

	private final String val;

	@JsonCreator
	public StringField(@JsonProperty("val") String val) {
		this.val = val;
	}

	public String getVal() {
		return val;
	}
}

public class IntField extends Field {

	private final int val;

	@JsonCreator
	public IntField(@JsonProperty("val") int val) {
		this.val = val;
	}

	public int getVal() {
		return val;
	}
}

Using Jackson on standard desktop Java SE, the following code works as expected:

String serialized = new ObjectMapper().writeValueAsString(r);
new ObjectMapper().readValue(serialized, Record.class);

However, that is not the case on Android (using the AndroidRecordModule), where the deserializer is oblivious of all class annotations (and, consequently, the JsonTypeInfo), and thus is unable to deserialize the abstract type Field. (abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information)

I would presume that the issue lies within this code: https://github.com/FasterXML/jackson-modules-base/blob/9b2fe00f8f724749e6ba2c93efe1b26d3f33442b/android-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java#L153since in this case, neither the TypeDeserializer, nor getClassAnnotations are used for Field.

My current workaround is to wrap objects akin to Field in this example into the following:

public class ContainerObject<T> { //Android Studio's opinion of this code is "Class can be converted to record class". Well, if only I could:)
    public final T value;

    @JsonCreator
    public ContainerObject(@JsonProperty("value") T value) {
        this.value = value;
    }
}

and then change Record to be

public record Record(ContainerObject<Field> field) {
}

Using this wrapper, deserialization works as expected on Android. However, the general issue seems to be a bug within Jackson's AndroidRecordModule.

If so desired, I'm willing to fix this myself and PR it.

HelloOO7 avatar Jul 25 '24 16:07 HelloOO7

Hi @HelloOO7, thanks for reporting this. I would appreciate a PR with a fix, and I'll gladly review it (though I can't approve). If you could, please check if it fixes any tests in the failing package.

eranl avatar Jul 25 '24 19:07 eranl

On an unrelated note, out of curiosity, any reason why, in your sample class hierarchy, Field shouldn't be an interface and StringField & IntField records?

eranl avatar Jul 27 '24 19:07 eranl

Would be interesting to have a test for regular Java SDK Records with similar set up (for jackson-databind). I assume that should work but I am not sure this is covered.

cowtowncoder avatar Jul 27 '24 20:07 cowtowncoder

any reason why, in your sample class hierarchy, Field shouldn't be an interface and StringField & IntField records

In this specific sample, no. In some of my real use cases, however, I use member inheritance for fields and base methods, which, although mostly substitutable through interface default methods, I find preferable to using records.

Would be interesting to have a test for regular Java SDK Records with similar set up

In this case, the problem was that the ValueInstantiator that was being used simply never resolved the actual polymorphic type, which, in standard Jackson deserialization flow, should most likely not happen.

the deserializer is oblivious of all class annotations

I would also like to correct myself on this statement - it appears that type annotations are preserved properly, only the resolution of the type info was actually flawed.

HelloOO7 avatar Jul 28 '24 10:07 HelloOO7