1
class JacksonError(
    val x: String,
    val isSomething: Boolean
)

fails with an error

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "something" (class ch.cypherk.blah.JacksonError), not marked as ignorable (2 known properties: "x", "isSomething"])

whereas there's no such issue with

class NoJacksonError(
    val x: String,
    val something: Boolean
)

My first instinct was that Jackson does not understand isXY-getters and that it simply needs the getters to start with get.

Interestingly, however, there's also no issue with

class AlsoNoJacksonError (
    val x: String,
    var isSomething: Boolean
)

Which means I have no idea what's happening.

JacksonError gets compiled to

public final class ch.cypherk.blah.JacksonError {
  public final java.lang.String getX();
    Code:
       0: aload_0
       1: getfield      #11                 // Field x:Ljava/lang/String;
       4: areturn

  public final boolean isSomething();
    Code:
       0: aload_0
       1: getfield      #18                 // Field isSomething:Z
       4: ireturn

  public ch.cypherk.blah.JacksonError(java.lang.String, boolean);
    Code:
       0: aload_1
       1: ldc           #21                 // String x
       3: invokestatic  #27                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #30                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: aload_1
      12: putfield      #11                 // Field x:Ljava/lang/String;
      15: aload_0
      16: iload_2
      17: putfield      #18                 // Field isSomething:Z
      20: return
}

NoJacksonError gets compiled to

public final class ch.cypherk.blah.NoJacksonError {
  public final java.lang.String getX();
    Code:
       0: aload_0
       1: getfield      #11                 // Field x:Ljava/lang/String;
       4: areturn

  public final boolean getSomething();
    Code:
       0: aload_0
       1: getfield      #19                 // Field something:Z
       4: ireturn

  public ch.cypherk.blah.NoJacksonError(java.lang.String, boolean);
    Code:
       0: aload_1
       1: ldc           #22                 // String x
       3: invokestatic  #28                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #31                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: aload_1
      12: putfield      #11                 // Field x:Ljava/lang/String;
      15: aload_0
      16: iload_2
      17: putfield      #19                 // Field something:Z
      20: return
}

So far, this meets my expectations; JacksonError has an isSomething() getter whereas NoJacksonError has a getSomething() getter.

But then AlsoNoJacksonError gets compiled to

public final class ch.cypherk.blah.AlsoNoJacksonError {
  public final java.lang.String getX();
    Code:
       0: aload_0
       1: getfield      #11                 // Field x:Ljava/lang/String;
       4: areturn

  public final boolean isSomething();
    Code:
       0: aload_0
       1: getfield      #18                 // Field isSomething:Z
       4: ireturn

  public final void setSomething(boolean);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #18                 // Field isSomething:Z
       5: return

  public ch.cypherk.blah.AlsoNoJacksonError(java.lang.String, boolean);
    Code:
       0: aload_1
       1: ldc           #24                 // String x
       3: invokestatic  #30                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #33                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: aload_1
      12: putfield      #11                 // Field x:Ljava/lang/String;
      15: aload_0
      16: iload_2
      17: putfield      #18                 // Field isSomething:Z
      20: return
}

which also has an isSomething() getter...

So why does JacksonError produce the exception?

And how do I make Jackson recognise the property correctly?

User1291
  • 7,664
  • 8
  • 51
  • 108

1 Answers1

1

There are two factors to solving the problem .
First :
Jackson by default uses Java bean naming conventions to imply json field names (For a boolean field, what is the naming convention for its getter/setter?) if no @JsonProperty field is specified. I tested the code out and the serialization works for the JacksonError class as there is a getter which aligns with Java naming standards for bean accessors. However while deserialization it looks to access a getter by the name "setSomething()". If you observe , the classes which have the setSomething method, dont give a deserialization error.

Second: The reason setSomething() method was not generated for your JacksonError class is because its a val field and it doesnt generate a setter (val fields are immutable and are initialized only in the constructor, hence no setter). Like @ Erwin Bolwidt suggested, you could set the below properties to the object mapper to include the kotlin module for jackson

 val mapper = ObjectMapper().registerKotlinModule() 
        .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
        .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.NONE)
        .setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
        .setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
        .setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE)

ObjectMapper().registerKotlinModule : This registers the module that helps with serialization/deserialization of kotlin classes. Git link (https://github.com/FasterXML/jackson-module-kotlin).

setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) : Makes all fields serializable, including private fields (In your case, the val fields)

        .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.NONE)  :  Makes sure any static / factory constructors are not autodetected

        .setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
        .setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
        .setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE) :  

All these props inform the object mapper to ignore autodetection of any getters/setters and boolean setters

Refer to the below thread for more info : Usage of Jackson @JsonProperty annotation for kotlin data classes

Arpan Kanthal
  • 475
  • 2
  • 7
  • The thread you link to doesn't really contain "more info" . What does setting the visibilities the way you propose actually do? – User1291 Jul 25 '19 at 14:36
  • @User1291 I have added the changes to my asnwer specifiying what each visibility modifier does. – Arpan Kanthal Jul 25 '19 at 15:39