66

kotlin 1.2.10 jackson-module-kotlin:2.9.0

I have the following data class in kotlin:

data class CurrencyInfo(
        @JsonProperty("currency_info") var currencyInfo: CurrencyInfoItem?
)

@JsonInclude(JsonInclude.Include.NON_NULL)
data class CurrencyInfoItem(
        @JsonProperty("iso_4217") var iso4217: String?,
        @JsonProperty("name") var name: String?,
        @JsonProperty("name_major") var nameMajor: String?,
        @JsonProperty("name_minor") var nameMinor: String?,
        @JsonProperty("i_ma_currency") var iMaCurrency: Int?,
        @JsonProperty("i_merchant_account") var iMerchantAccount: Int?,
        @JsonProperty("i_x_rate_source") var iXRateSource: Int?,
        @JsonProperty("base_units") var baseUnits: Double?,
        @JsonProperty("min_allowed_payment") var minAllowedPayment: Int?,
        @JsonProperty("decimal_digits") var decimalDigits: Int?,
        @JsonProperty("is_used") var isUsed: Boolean?
)

When I try to deserialize this data class I get the following:

{"currency_info":{"iso_4217":"CAD","name":"Canadian Dollar","imerchantAccount":0,"ixrateSource":2}}

As you can see, the last two options were deserialized incorrectly. This issue could be solved by adding directly annotation to getter @get:JsonProperty. However, according to jackson docs @JsonProperty should be assigned to getters/setters/fields

So, I want to ask is there a reliable way to annotate property for jackson in kotlin to have correct serialization/deserialization (moreover all my data classes are autogenerated, so it would be hard to create some two/three lines annotations, separately for getter and setter)

Otherwise, could this issue be resolved by some jackson settings?

According to answers below, the following works for me

private 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)
Mike
  • 725
  • 1
  • 8
  • 11
  • you need just need .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE). Issue depends on specific handling of "is" getters – Beloo Feb 15 '19 at 12:48

7 Answers7

94

@JsonProperty annotations in your code are all put on private fields within your data class and by default Jackson doesn't scan private fields for annotations. You have to instruct it to do otherwise by putting @JsonAutoDetect annotation:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
data class CurrencyInfo(
    @JsonProperty("currency_info") var currencyInfo: CurrencyInfoItem?
)

or alternatively you can move your annotations on accessor methods:

data class CurrencyInfo(
    @get:JsonProperty("currency_info") var currencyInfo: CurrencyInfoItem?
)
moffeltje
  • 4,521
  • 4
  • 33
  • 57
Alex
  • 7,460
  • 2
  • 40
  • 51
  • Thanks, I set the visibility modifier globally – Mike Dec 27 '17 at 15:25
  • 1
    I have `jackson-annotations-2.9.0` and there is no `PRIVATE` option. I tried with `ANY`, but the property is still ignored. [View Source code](https://github.com/FasterXML/jackson-annotations/blob/master/src/main/java/com/fasterxml/jackson/annotation/JsonAutoDetect.java) – Mario Trucco Aug 28 '18 at 08:49
  • 3
    The [answer down below](https://stackoverflow.com/a/54977519/3859199) that uses `@param:JsonProperty("")` is also critical if you're trying to replicate this for something that doesn't happen when you are getting the value, i.e. custom Json Deserializers that happen on construction. – Bwvolleyball Sep 11 '19 at 21:23
  • not working for me any of this – Richa Shah Nov 17 '21 at 13:44
  • Hi, in Java it is possible to have different jsonproperty names while serializing and deserializing. Is it possible to do similar thing for fields in Kotlin data class? I see `@get:JsonProperty` but not `@set:Jsonproperty`. https://stackoverflow.com/questions/8560348/different-names-of-json-property-during-serialization-and-deserialization – firstpostcommenter Jul 09 '22 at 21:02
  • @firstpostcommenter yes, there's a set target for kotlin annotations: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets – Alex Jul 10 '22 at 03:17
  • I tried giving 2 different jsonProperty names to @get and @set but then I get the error `Conflicting/ambiguous property name definitions.... found multiple explicit names` – firstpostcommenter Jul 10 '22 at 07:18
20

You can do something like this:

data class CurrencyInfo @JsonCreator constructor (
        @param:JsonProperty("currency_info") 
        @get:JsonProperty("currency_info")
        val currencyInfo: CurrencyInfoItem?
)

code above translates to java as:

public final class CurrencyInfo {
   @Nullable
   private final String currencyInfo;

   @JsonProperty("currency_info")
   @Nullable
   public final String getCurrencyInfo() {
      return this.currencyInfo;
   }

   @JsonCreator
   public CurrencyInfo(@JsonProperty("currency_info") @Nullable String currencyInfo) {
      this.currencyInfo = currencyInfo;
   }
}

code from accepted answer translates to java as following:

First (is not pure immutable):

@JsonAutoDetect(
   fieldVisibility = Visibility.ANY
)
public final class CurrencyInfo {
   @Nullable
   private String currencyInfo;

   @Nullable
   public final String getCurrencyInfo() {
      return this.currencyInfo;
   }

   public final void setCurrencyInfo(@Nullable String var1) {
      this.currencyInfo = var1;
   }

   public CurrencyInfo(@JsonProperty("currency_info") @Nullable String currencyInfo) {
      this.currencyInfo = currencyInfo;
   }
}

Second (probably has problems with deserialization):

public final class CurrencyInfo {
   @Nullable
   private final String currencyInfo;

   @JsonProperty("currency_info")
   @Nullable
   public final String getCurrencyInfo() {
      return this.currencyInfo;
   }

   public CurrencyInfo(@Nullable String currencyInfo) {
      this.currencyInfo = currencyInfo;
   }
}
Maksim Turaev
  • 4,115
  • 1
  • 29
  • 42
17

You can add the jackson-module-kotlin (https://github.com/FasterXML/jackson-module-kotlin) and register the kotlin module with jackson:

val mapper = ObjectMapper().registerKotlinModule()

Then it (and many other things) works automagically.

GameScripting
  • 16,092
  • 13
  • 59
  • 98
  • 2
    It was used from the very beginning. You could even check the initialization in the question post. – Mike Oct 12 '18 at 12:39
  • 1
    @Mike yeah, right I missed that one. It however did the job for me. – GameScripting Oct 23 '18 at 10:44
  • By far it is the best answer here: registering this module not only resolves the issue without needing to type `@get:` or use other tweaks, but improves Kotlin<>Jackson interop overall. – madhead Sep 19 '19 at 09:54
  • KotlinModule() does not in the last version of jackson unfortunately but all docs talk about use of KotlinModule() unfortunately. nobody says how to do that in latest version – metis Apr 13 '22 at 12:14
  • @metis see https://github.com/FasterXML/jackson-module-kotlin#usage: `ObjectMapper().registerKotlinModule()` – GameScripting Apr 13 '22 at 14:41
5

Kotlin doesn't support @param and @get annotations as one annotation, so we have to write such code:

data class User(
    @param:JsonProperty("id") @get:JsonProperty("id") val id: Int,
    @param:JsonProperty("name") @get:JsonProperty("name") val name: String
)

Here you can tell JetBrain guys to support this feature and allow:

data class User(
    @JsonProperty("id") val id: Int,
    @JsonProperty("name") val name: String
)

https://youtrack.jetbrains.com/issue/KT-11005

Malachiasz
  • 7,126
  • 2
  • 35
  • 49
4

You can configure the ObjectMapper from the jackson library by calling the method setPropertyNamingStrategy(...)

Using PropertyNamingStrategy.SNAKE_CASE should resolve your problem

See also the other available strategies here : PropertyNamingStrategy

Prim
  • 2,880
  • 2
  • 15
  • 29
  • 3
    Thanks, it's a good idea, I have tried it. But default SNAKE_CASE translation has a poor implementation. A lot of fields are resolved incorrectly. For example, iso4217 -> not translated into iso_4217, iXRateSource -> i_x_rate_source and so on – Mike Dec 27 '17 at 12:23
3

this issue is resolved in

https://github.com/FasterXML/jackson-module-kotlin/issues/237

or you can also use

data class SignRequest    
    @param:JsonProperty("estamp_request")
    @get:JsonProperty("estamp_request")
    val eStamp: EstampRequest?
}

data class EstampRequest(
    val tags: Map<String,Int>
)
aSemy
  • 5,485
  • 2
  • 25
  • 51
naren2605
  • 106
  • 2
  • As far as I see this issue is NOT resolved at the time of my writing, but there are some workarounds in the comments. – Vic Apr 22 '22 at 15:51
1

I faced this problem today and what I did was register KotlinModule() in my ObjectMapper(). Bellows follow an example.

@Bean fun objectMapper = ObjectMapper().registreModule(KotlinModule())

Above it's a dummy objectMapper, I believe that you should put other configurations in your objectMapper like serializers and so on

Ivan Rodrigues
  • 441
  • 4
  • 20