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

Duplicate creator error when using combination of JsonProperty and JsonIgnore #692

Closed
FWest98 opened this issue Aug 2, 2023 · 7 comments

Comments

@FWest98
Copy link

FWest98 commented Aug 2, 2023

Describe the bug
When using both JsonProperty and JsonIgnore at the same time, creator resolving does not seem to succeed. This does work for Java, so I guess it's a specific thing with Kotlin properties, getters and/or setters.

To Reproduce

class Foo (
    @JsonProperty("bar")
    val alternateBar: String,

    @JsonIgnore
    val bar: String = "noDeserialization",
)

mapper.readValue<Foo>("""{ "bar": "foo" }""")

Trying to deserialize this results in the following error:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Duplicate creator property "bar" (index 0 vs 1) for type `nl.test.Foo` 
 at [Source: (String)"{ bar: "foo" }"; line: 1, column: 1]

Expected behavior
I expect the deserialization to succeed, with the value bar in field alternateBar.

Versions
Kotlin: 1.8.21
Jackson-module-kotlin: 2.15.2
Jackson-databind: 2.15.2

Additional context
Maybe this is already solved in #630? The linked issues there did not seem the same, but it might touch the same areas of code. Furthermore, this seems to have been in issue in the Java library at some point (https://groups.google.com/g/jackson-user/c/sXDF767Flj8), but in Java it now works with the following class definition:

class Test {
    @JsonProperty("bar")
    public String alternateBar;

    @JsonIgnore
    public String bar;
}
@FWest98 FWest98 added the bug label Aug 2, 2023
@k163377
Copy link
Contributor

k163377 commented Aug 4, 2023

In jackson-module-kotlin, the constructor is used to deserialize.
In other words, the Java code you are submitting is not equivalent to the Kotlin code.
I recommend that you decompile your code and check it.

It looks like you need to set a default argument for the parameter bar.

@FWest98
Copy link
Author

FWest98 commented Aug 4, 2023

The default value was indeed missing from my sample - the error also occurs with a default value set.

@k163377
Copy link
Contributor

k163377 commented Aug 4, 2023

It was an incorrect reference to the default argument.

When reproduced in Java, the situation is as follows.
You need to make sure that the bar parameter does not cover the name.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

public class Temp {
    public static class Foo {
        private final String foo;
        @JsonIgnore
        private final String bar;

        @JsonCreator
        public Foo(@JsonProperty("bar") String foo, @JsonProperty("bar") String bar) {
            this.foo = foo;
            this.bar = bar;
        }
    }

    @Test
    public void test() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.readValue("{\"bar\":\"bar\"}", Foo.class);
    }
}

@FWest98
Copy link
Author

FWest98 commented Aug 4, 2023

How would I solve the issue in my Kotlin code? My current workaround is to put @JsonProperty("ignored") on the ignored property, but if there is a way to do this more neatly that would be great!

@k163377
Copy link
Contributor

k163377 commented Aug 5, 2023

I don't understand what you are trying to do, but a simple rewrite would be as follows

class Foo (
    @JsonProperty("bar")
    val alternateBar: String
) {
    @JsonIgnore
    val bar: String = "noDeserialization"
}
class Foo (
    val alternateBar: String,
    @JsonIgnore
    val bar: String,
) {
    @JsonCreator
    constructor(@JsonProperty("bar") alternateBar: String) : this(alternateBar, "noDeserialization")

}

@k163377 k163377 removed the bug label Aug 6, 2023
@FWest98
Copy link
Author

FWest98 commented Aug 6, 2023

My specific usecase is that I have a data class implementing an interface that defines a property ("bar") that corresponds to one of my JSON fields by name, but that should not be mapped. So I want to put that property in my constructor (to give it the value semantics), but prevent it from being mapped by Jackson, so I give it the @JsonIgnore annotation.

So essentially:

class Impl (
    @JsonProperty("bar")
    val alternateBar: String,

    @JsonIgnore
    override val bar: String = "noDeserialization",
) : BarInterface

Then I would expect this to just work out of the box, without doing manual constructor magic or losing data class semantics. I understand now that this has to do with constructor/property mapping magic in Kotlin, but the weird thing is that the following does work properly:

class Impl (
    @JsonProperty("bar")
    val alternateBar: String,

    @JsonIgnore
    @JsonProperty("ignoredBar")
    override val bar: String = "noDeserialization",
) : BarInterface

So to me it looked like the ignore annotation did not get registered in some way, while the property annotation does seem to have effect. That's why I registered this as a bug.

@k163377
Copy link
Contributor

k163377 commented Aug 8, 2023

At least this is not a bug.
Also, we do not plan to implement a feature that only works in edge cases like this.

Therefore, this issue is closed.

@k163377 k163377 closed this as completed Aug 8, 2023
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