Skip to content
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

Issue Serializing an Immutable Class with @JsonCreator #1529

Closed
haskovec opened this issue Feb 17, 2017 · 4 comments
Closed

Issue Serializing an Immutable Class with @JsonCreator #1529

haskovec opened this issue Feb 17, 2017 · 4 comments

Comments

@haskovec
Copy link

haskovec commented Feb 17, 2017

If you create a Immutable class that you want to serialize out with Jackson and the output needs a different fieldName than the class has it will serialize it with the fieldName and not the name you passed in @JsonProperty("") meanwhile if you have the property annotated with @JsonProperty it outputs the correct field name. Consider the following class:

public class BrokenDTO {
    private final String firstName;

    private final String lastName;

    @JsonCreator
    public BrokenDTO(
            @JsonProperty("first_name")
            final String firstName,
            @JsonProperty("last_name")
            final String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

The class will be output as:

{"firstName":"Jeffrey","lastName":"Haskovec"}

On the otherhand the following class will be serialized correctly:

public class WorkingDTO {
    @JsonProperty("first_name")
    private final String firstName;

    @JsonProperty("last_name")
    private final String lastName;

    @JsonCreator
    public WorkingDTO(
            @JsonProperty("first_name")
            final String firstName,
            @JsonProperty("last_name")
            final String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

With the output of:

{"first_name":"Jeffrey","last_name":"Haskovec"}

I noticed in Debugging the code in BeanSerializerFactory that in removeIgnorableTypes it shows the values of the fields in the Constructor, however property.getAccessor() returns null which results in those @JsonProperty's being removed.

I created a test program to quickly demonstrate the issue (I did it in Spring Boot since that is where we found the issue in production). You can just run mvn test to demonstrate the output of the classes:

https://github.com/haskovec/jackson-serialization-bug

@cowtowncoder
Copy link
Member

I suspect may not be aware of one limitation regarding JVM and bytecode: that of method (including constructor) parameters not retaining names in bytecode (at least before Java 8, in which they may be included, depending on compiler settings).

Because of this, code:

public BrokenDTO(
            @JsonProperty("first_name")
            final String firstName,
            @JsonProperty("last_name")
            final String lastName) {
}

does not rename property from "firstName" into "first_name", but rather assigns a name for sort of unnamed parameter.
As a result there is no information to tie constructor parameters with getters, and effectively you have two sets of properties for deserialization: both firstName and first_name are "known".

With Java 8 things change, and this is why jackson-module-parameter-names was added: it adds parameter-name introspection capability. So if you register this module, it should be able to find parameter names (assuming compiler flags are set to retain them), and your set up should work.
If things do not work with this module registered (and with up to date version), that would be wrong. I think there are unit tests to cover this use case and I am not aware of bugs in this area.

@haskovec
Copy link
Author

Thanks for that comment, I am actually compiling the parameter names in as this is a Java 8 project, but I was unaware of the jackson-module-parameter-names I was just under the impression if the data was there it would use it. I will retest with that module and see if it fixes it.

@haskovec
Copy link
Author

That fixes my problem! Thank you so much for your help @cowtowncoder!

@cowtowncoder
Copy link
Member

@haskovec Glad to hear it works.

I agree in that it is unfortunate this kink is there; JDK extension API that is required to get parameter names was only added in Java 8. And since for now (Jackson 2.x) JDK baseline is JDK 6 (for runtime; for compilation JDK 7 is needed) it is not possible to include functionality directly.

But with Jackson 3.x we should be able to integrate this small part of functionality into core jackson-databind so this should become lesser issue.

I wonder if there's a way to improve javadocs; maybe mention this for annotations or something.

Anyway: thank you for reporting this; it might help others.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants