Jackson cannot deserialize some non-empty fields - kotlin

I have a problem with the object deserialization.
My DTO contains a list of Pairs (the previous version was Map).
data class MyDto(
#JsonIgnoreProperties(ignoreUnknown = true)
val myField: List<Pair<String, Boolean>>?
)
And I constantly receive a MissingKotlinParameterException
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class kotlin.Pair<java.lang.String,java.lang.Boolean>] value failed for JSON property first due to missing (therefore NULL) value for creator parameter first which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 9, column: 33] (through reference chain: my.path.MyDto[\"field\"]->java.util.ArrayList[0]->kotlin.Pair[\"first\"])\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:162)
my json looks like:
{
"myField" : [
"A": true,
"B": false
]
}
As you can see I have already made the list nullable and put an annotation#JsonIgnoreProperties. But still I get the error.
My configuration for the objectMapper
#Bean
#Primary
fun objectMapper(): ObjectMapper = jacksonObjectMapper().apply {
findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
.disable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.enable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
}
What should I also enable/disable to make it work?

Try replacing
List<Pair<String, Boolean>>
with
Map<String, Boolean>
And change the JSON to:
{
"myField" : {
"A": true,
"B": false
}
}
Should work as intended now.

Related

Deserialize empty json value to null with kotlinx.serialization

I have the following response from a backend:
{
"title": "House",
"translations": {
"es": "Casa",
"fr": "Maison",
"de": "Haus"
}
}
To process it I am using the kotlinx serializer and this is my data class.
#Serializable
data class MyRespons(
val title: String,
val translations: Map<String,String>? = null,
)
The property translations is optional, so in some cases I can just get the title (which is fine). What the problem is, is that there also cases where the backend returns this json:
{
"title": "House",
"translations": ""
}
This throws an error because Kotlin is not converting the empty string to a null map but tries to get the properties from it. Is there a way to make Kotlin treat an empty string as if the property was not set at all? (I am trying to not make a custom serializer for this, especially because the map serializer has lots of code...)
Sadly I can't change this backend behavior and have to live with it.
you can wrap Map Serializer with:
object MapSerializer: KSerializer<Map<String,String>> {
override val descriptor: SerialDescriptor
get() = TODO("Not yet implemented")
override fun deserialize(decoder: Decoder): Map<String, String> {
if (decoder.decodeString().isEmpty())
return mapOf()
else
return MapSerializer(String.serializer(),String.serializer()).deserialize(decoder)
}
}

Populate data classes with values from linkedhashmap

I have a linkedhashmap that has the following shape: <String, Subject>. The class Subject has the following fields:
class Subject {
var name: Boolean? = null
var lastname: Boolean? = null
var location: Boolean? = null
..
}
final_result =
"admin" -> Subject
"customer" -> Subject
etc.
I need to populate data classes that have the following format:
data class SubjectSummary(
val admin: SubjectData,
val customer: SubjectData
...
)
data class SubjectData(val details: DetailsData)
data class DetailsData(val name:String, val lastName:String ...)
Because I need to serialize the SubjectSummary class and get the following json format:
{
"admin": {
"details": {
"name": "",
"lastname": "",
...
}
}
"customer": {
"details": {
"name": "",
"lastname": "",
...
}
}
}
How do I assign the final_result map to match the SubjectSummary structure? I have done it with simple data classes, but when the fields within the data class are data classes, I'm not sure hot to populate it. Any guidance?
For simplicity I'm only showing a small example with a few fields.
If your goal with this transformation is just to be able to serialize with the given JSON format, then you don't need this SubjectSummary class. A Map<String, SubjectData> would be sufficient and probably more convenient to create when transforming from the initial map.
Also, it seems that DetailsData contains the same fields as Subject. If that's the case there is no need for an extra class either.
So in the end it seems you just need to create a Map<String, SubjectData where SubjectData could be defined as data class SubjectData(val details: Subject). You can transform your initial map pretty easily then:
val transformed = finalResult.mapValues { (_, subject) -> SubjectData(subject) }

Kotlinx Serialization MissingFieldException

I am in the process of converting from Moshi to kotlinx serialization with Ktor and when I try to make a request to get data I am getting this error
kotlinx.serialization.MissingFieldException: Field 'attachments' is
required, but it was missing
which makes sense since this specific response does not contain this field
Response Json
{
"data": {
"id": "1299418846990921728",
"text": "This is a test"
}
}
BUT my Serialized class has the attachments field as nullable (it is in the response only when it needs to be) so it should just ignore it I thought like it did with Moshi
#Serializable
data class ResponseData(
val id: Long
val attachments: Attachments?,
val author_id: String?,
val text: String
}
In my Ktor client setup I have it set to ignore unknown keys
private val _client: HttpClient = HttpClient(engine) {
install(JsonFeature) {
val json = Json {
this.isLenient = true
this.ignoreUnknownKeys = true
}
serializer = KotlinxSerializer(json)
}
}
Why is it still saying that the field is required even though its nullable?
I figured it out, apparently even though you mark something as nullable its still considered required.
For it to truly be optional you need to give it a default value so for example the data class would look like this with the nullables
#Serializable
data class ResponseData(
val id: Long
val attachments: Attachments? = null,
val author_id: String? = null,
val text: String
}
once you set the value the fields becomes optional and wont throw that exception
As of v1.3.0, you can configure the Json feature to treat absent fields as null, with explicitNulls = false
install(JsonFeature) {
serializer = KotlinxSerializer(
json = kotlinx.serialization.json.Json {
explicitNulls = false
}
)
}
The documentation for explicitNulls:
Specifies whether null values should be encoded for nullable properties and must be present in JSON object during decoding.
When this flag is disabled properties with null values without default are not encoded; during decoding, the absence of a field value is treated as null for nullable properties without a default value.
true by default.
I had the same exception with the Spring RestController, where incase of missing data, I need to send BAD_REQUEST response. I could achieve this with the following code.
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = [SerializationException::class])
open fun handleRequestError(ex: SerializationException) = //
log.warn("Unable to process incomplete request", ex)

GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY

Facing this problem past few days.Does any guys check this issue? Any help would be greatly appreciated.How can I solved this problem?
GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY
Problem coming from
override fun onSuccess(str_SUCCESS: String)
{
System.out.println("JSON_IS"+str_SUCCESS)
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(str_SUCCESS, PaymentScheduleModel::class.java) // Problem here
}
Json Response is
{
"status": {
"statusCode": 10016,
"isSuccess": true,
"message": "Success"
},
"data": {
"payback_schedule": [
{
"id": 2,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "05/01/2018 12:31:10",
"e_date": "11/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
},
{
"id": 3,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "12/01/2018 12:31:10",
"e_date": "18/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
}
]
}
}
PaymentScheduleModel
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: PaymentSchedule)
data class PaymentSchedule
(#SerializedName("id") val id: Int,
#SerializedName("paid_amount") val paid_amount:String,
#SerializedName("paidStatus") val paidStatus:String,
#SerializedName("paid_status") val paid_status:String,
#SerializedName("s_date") val s_date:String,
#SerializedName("e_date") val e_date:String,
#SerializedName("current_balance") val current_balance:String,
#SerializedName("payanytime_button_status") val payanytime_button_status:String,
#SerializedName("btnColor") val btnColor:String,
#SerializedName("btnHexColor") val btnHexColor:String)
Your model object does not match your Json.
You are trying to parse a JsonObject PaymentScheduleModel which has sub object "payback_schedule" of type PaymentSchedule but you have a JsonObject which has a sub object "data" which is what has the sub object "payback_schedule". So really, you want to parse the "data" block.
You have two options:
1: Create another model that wraps the data block and parse that:
data class PaymentScheduleData(#SerializedName("data") val payback_schedule_model: PaymentScheduleModel)
override fun onSuccess(str_SUCCESS: String) {
val paymentScheduleData = Gson().fromJson<PaymentScheduleData>(str_SUCCESS, PaymentScheduleData::class.java)
// Now use paymentScheduleData.payback_schedule_model
}
2: Pull out the data portion first, then parse:
override fun onSuccess(str_SUCCESS: String) {
// Get the root JsonObject
val jsonObject = Gson().fromJson<JsonObject>(str_SUCCESS, JsonObject::class.java)
// Get the "data" block that matches the model and parse that
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(jsonObject.getAsJsonObject("data"), PaymentScheduleModel::class.java)
}
Hope that helps!
The error is telling you that payback_schedule is holding an array instead of object. So, payback_schedule should be Array<PaymentSchedule> instead of PaymentSchedule.
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: Array<PaymentSchedule>)
PS. You are suggested to implement your own equals() and hashCode() function if your data class contains Array because the default implementation of Array's equals() function compares the referential equality. Suggested reading: Equals method for data class in kotlin

Register deserializer for a wrapped List of type

I have JSON that is like this:
{
"apps": [
{
"id": "1",
...
},
{
"id": "2",
...
}
]
}
And for example say the Application class looks like this
data class Application(
val id: String
)
I want to deserialize the JSON into a List<Application>, where each {...} is an Application. I was hoping to do this without having to create a wrapper class like Applications, annotating it with #JsonRootName, and then enabling DeserializationFeature.UNWRAP_ROOT_VALUE. The end goal is to have a Retrofit interface that has something like:
#GET("api/apps")
fun listApplications(): Call<List<Application>>
I tried to implement a simple JsonDeserializer (could probably be optimized):
class ApplicationListDeserializer
: JsonDeserializer<List<Application>>() {
companion object {
private val COLLECTION_TYPE: CollectionType = TypeFactory.defaultInstance()
.constructCollectionType(List::class.java, Application::class.java)
}
override fun deserialize(parser: JsonParser, context: DeserializationContext): List<Application> {
val mapper = ObjectMapper()
val node: JsonNode = parser.codec.readTree(parser)
val collectionReader = mapper.readerFor(COLLECTION_TYPE)
return collectionReader.readValue(node.get("apps"))
}
}
I don't see any way to register this deserializer for this specific type. I tried this:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
val objectMapper = ObjectMapper().registerModule(KotlinModule())
val module = SimpleModule()
module.addDeserializer(List::class.java, ApplicationListDeserializer())
objectMapper.registerModule(module)
val applications: List<Application> = objectMapper.readValue("""{
"apps": [
{
"id": "/api-catalog-backend"
}
]
}""")
But that fails with:
Can not construct instance of Application: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: N/A; line: -1, column: -1] (through reference chain: java.util.ArrayList[0])" type="com.fasterxml.jackson.databind.JsonMappingException"><![CDATA[com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of Application: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: N/A; line: -1, column: -1] (through reference chain: java.util.ArrayList[0])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
I am using the following dependency versions:
compile("com.fasterxml.jackson.core", "jackson-core", "2.8.6")
compile("com.fasterxml.jackson.module", "jackson-module-kotlin", "2.8.4")
compile(kotlinModule("stdlib", "1.1-M03"))
compile(kotlinModule("reflect", "1.1-M03"))
How do I configure the deserialization to work correctly with a List<Application>?