Jackson prefers a private constructor over @JsonCreator when deserializing a class using @JsonValue

I have a simple class with a private constructor and a static factory. I want the class to serialize as a number, so I annotated the getter for the field using @JsonValue . However, Jackson prefers a private constructor over a static factory, even when I set the static factory annotation with @JsonCreator . It works if I annotate a private constructor with @JsonIgnore , but that doesn't work a bit.

I saw several posts claiming that @JsonCreator only works if the parameters are annotated using @JsonProperty ; however, this is similar to objects serialized as JSON objects. This object is serialized as a number, and therefore there is no property for annotation.

Is there something I am missing?

class example:

 package com.example; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.google.common.base.Preconditions; public class NonNegative { private final double n; private NonNegative(double n) { this.n = n; } @JsonCreator public static NonNegative checked(double n) { Preconditions.checkArgument(n >= 0.0); return new NonNegative(n); } @JsonValue public double getValue() { return n; } @Override public int hashCode() { return Objects.hash(n); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof NonNegative) { NonNegative that = (NonNegative) obj; return Objects.equals(n, that.n); } return false; } } 

Test Examples:

 package com.example; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; public class NonNegativeTest { private static final ObjectMapper MAPPER = new ObjectMapper(); @Test public void itSerializesAndDeserializes() throws Exception { NonNegative nonNegative = NonNegative.checked(0.5); assertThat(MAPPER.readValue(MAPPER.writeValueAsString(nonNegative), NonNegative.class)).isEqualTo(nonNegative); } /* This test fails. */ @Test(expected = JsonMappingException.class) public void itDoesNotDeserializeANegativeNumber() throws Exception { MAPPER.readValue(MAPPER.writeValueAsString(-0.5), NonNegative.class); } } 
+7
java json jackson
source share
1 answer

Indeed, Jackson will override the JsonCreator method using the constructor method in case the parameter is standard for Java. I would say that this is an error in the BasicDeserializerFactory # _handleSingleArgumentConstructor method.

So, the problem is that the constructor takes precedence over the static factory method if this constructor and static factory method are of the usual Java type. There are several ways to resolve them.

Set creator visibility level to NON_PRIVATE:

 @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NON_PRIVATE) class NonNegative { 

The second way is to remove the static factory method and use the constructor. I moved Preconditions.checkArgument to the constructor (it does little ... Just throw an IllegalArgumentException if the condition is not met):

 public class NonNegative { private final double n; private NonNegative(double n) { Preconditions.checkArgument(n >= 0.0); this.n = n; } @JsonValue public double getValue() { return n; } } 

Another way is to use the @JsonIgnore annotation, but you say you don't like this approach :)

Update I registered a bug: https://github.com/FasterXML/jackson-databind/issues/660

Update Fixed a Jackson bug that prefers the constructor using the static factory method: https://github.com/FasterXML/jackson-databind/commit/257ae1c7a88c5ccec2882433a39c0df1de2b73aa

+11
source share

All Articles