Hello! I've created a project with Ktor 2.3.12 and...
# ktor
k
Hello! I've created a project with Ktor 2.3.12 and I'm using Jackson (io.ktor:ktor-serialization-jackson) to serialize/deserialize. I'm receiving the following json:
Copy code
{
  "new-york-store": [
    {
      "open": 14
    },
    {
      "close": 22
    }
  ],
  "new-jersey-store": [
    {
      "open": 10
    },
    {
      "close": 18
    }
  ]
}
that I've mapped this way:
Copy code
enum class Status(val description: String) {

    OPEN("open"),
    CLOSED("closed")
}


data class StoreOpenHours(val type: Status?, val hour: Long?) {

}

data class Store(val name: String) {

}

data class Response(
    val hours: MutableMap<Store, List<StoreOpenHours>?> = sortedMapOf<Store, List<StoreOpenHours>?>(),
) {
But I'm receiving the following error
Copy code
io.ktor.serialization.JsonConvertException: Illegal json parameter found:
Unrecognized field "open" (class storeservice.model.StoreOpenHours), not marked as ignorable (2 known properties: "type", "hour"])
Do you have any idea how can I solve this? I've already tried many things, like for example use @JsonValue annotation "type". But nothing worked.
r
Your JSON is supposed to be mapped to an object with an integer property named "open" and another named "close". Try creating a data class as such:
Copy code
data class StoreOpenHours(
  val open: Int, 
  val close: Int
)
And if you want a dynamic property names for each store use
Map<String, List<StoreOpenHours>>
. I'm not familiar with the remaining parts of your code, but as far as serialization with Jackson goes, this should do the trick.
k
Thanks for your help. I would like the "open" to be mapped as "open" and not as an int. Also sometimes I receive the json like this:
Copy code
{
  "new-york-store": [
    {
      "open": 14
    }
  ]
}
r
As long as close property is nullable in your data class and you configure Jackson either globally or in your data class to ignore nulls, you're good to go. 🙂 As for mapping as open and not int, not sure I follow. Each property in JSON has a name and a type, you map that in Kotlin to a property that also has a name and a type. In your example, the property name is open, the value is 14 (int). Could you clarify what you're trying to achieve?
k
I'm consuming an internal API that sends me the data in this format:
Copy code
{
  "new-york-store": [
    {
      "open": 14
    },
    {
      "close": 22
    }
  ],
  "new-jersey-store": [
    {
      "open": 10
    },
    {
      "close": 18
    }
  ]
}
On my side I need to combine this information about the opening hours with other information and create an endpoint that returns the data with the "enhanced information".
That why I would like to do the conversion with the data classes that I provided. I tried to create custom serializer/deserializer but this also didn't work. 😞
r
I see. This is usually solved in steps. 1. Map the incoming response to an object as I shared (this class that maps responses is called a DTO or Data Transfer Object), that's how Jackson work (and most other serialization libs). 2. Map the DTO to another class as needed for your custom endpoint.
k
@Renan Kummer, thank you so much for your help. I found the issue on my mapping. Now I'm mapping the following way:
Copy code
enum class Status(val description: String) {

    OPEN("open"),
    CLOSE("close");

    companion object {
        fun fromString(value: String): Status {
            return entries.find { it.name.equals(value, ignoreCase = true) }
                ?: throw IllegalArgumentException("Unknown Status: $value")
        }
    }
}

@JsonDeserialize(using = StoreOpenHoursDeserializer::class)
data class StoreOpenHours(val hours: Map<Status, Long) {

}

data class Store(val name: String) {

}

data class Response(
    val hours: MutableMap<Store, List<StoreOpenHours>> = sortedMapOf<Store, List<StoreOpenHours>>(),
) {}

}

data class ClientResponse(
    val openingHours: MutableMap<DayOfWeek, List<OpeningHours>> = sortedMapOf<DayOfWeek, List<OpeningHours>>(),
) {
    @JsonAnySetter
    fun values(
        name: String,
        hours: List<OpeningHours>,
    ) {
        if (hours.isEmpty()) {
            openingHours[DayOfWeek.valueOf(name.uppercase())] = Collections.emptyList()
        } else {
            openingHours[DayOfWeek.valueOf(name.uppercase())] = hours
        }
    }
}
I had to create a custom serializer and register it on my json configuration:
Copy code
addModule(SimpleModule().addDeserializer(StoreOpenHours::class.java, StoreOpenHoursDeserializer()))
Now everything is working as I expected. :-)