Skip to content

jClass annotations and polymorphic types are ignored when deserializing Android Record fields #248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
HelloOO7 opened this issue Jul 25, 2024 · 4 comments · Fixed by #249
Labels

Comments

@HelloOO7
Copy link
Contributor

HelloOO7 commented Jul 25, 2024

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:

null, null, parameter.getAllAnnotations(), parameter, i, injectable, null);
since 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.

@eranl
Copy link
Contributor

eranl commented Jul 25, 2024

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
Copy link
Contributor

eranl commented Jul 27, 2024

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?

@cowtowncoder
Copy link
Member

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.

@HelloOO7
Copy link
Contributor Author

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.

@cowtowncoder cowtowncoder changed the title jackson-module-android-record: Class annotations and polymorphic types are ignored when deserializing record fields jClass annotations and polymorphic types are ignored when deserializing Android Record fields Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants