Kotlinx Serialization MissingFieldException - ktor

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)

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)
}
}

Kotlin get all property value of data class

Is there a syntactic sugar in Kotlin to iterate on each field/property value of a data class?
Sample:
data class User(
var firstName: String = DEFAULT_VALUE_STRING,
var middleName: String = DEFAULT_VALUE_STRING,
var lastName: String = DEFAULT_VALUE_STRING
)
val user = User()
Then check if any of the property's value is empty, considering all of it is String data type with something like this
if (user.properties.any{ it.isBlank() }) {
// TODO ...
}
Probably the closest you'll get is checking all the values of all the generated componentX() functions (since they're only created for the constructor parameter properties, the "data" in a data class) but yeah that involves reflection.
If I were you, I'd create an interface with a properties property and make all your data classes implement that - something like this:
import kotlin.reflect.KProperty0
interface HasStringProperties {
val properties: List<KProperty0<String>>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
override val properties = listOf(::firstName, ::middleName, ::lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.get().isBlank()})
}
So no, it's not automatic - but specifying which properties you want to include is simple, and required if you're going to access it on a particular class, so there's an element of safety there.
Also, because you're explicitly specifying String properties, there's type safety included as well. Your example code is implicitly assuming all properties on your data classes will be Strings (or at least, they're a type with an isBlank() function) which isn't necessarily going to be true. You'd have to write type-checking into your reflection code - if you say "I don't need to, the classes will only have String parameters" then maybe that's true, until it isn't. And then the reflection code has to be written just because you want to add a single age field or whatever.
You don't actually have to use property references in Kotlin either, you could just grab the current values:
interface HasStringProperties {
val properties: List<String>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
// getter function creating a new list of current values every time it's accessed
override val properties get() = listOf(firstName, middleName, lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.isBlank()})
}
It depends whether you want to be able to reference the actual properties on the class itself, or delegate to a getter to fetch the current values.
And of course you could use generics if you want, list all the properties and use filterIsInstance<String> to pull all the strings. And you could put a function in the interface to handle a generic isEmpty check for different types. Put all the "check these properties aren't 'empty'" code in one place, so callers don't need to concern themselves with working that out and what it means for each property

Jackson cannot deserialize some non-empty fields

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.

Inserting data into database returns MismatchedInputException error

I am trying to insert some data into the database, and am getting the following error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.joda.time.DateTime` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('2019-04-19')
My content negotiation
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}
And my model:
data class User(
//some codes
val registrationDate = DateTime // org.joda.time.DateTime
)
And when will I send by json:
{
//some other data
"registrationDate" : "2019-07-15"
}
Can someone help me, please?
You have to install the Joda module for Jackson https://github.com/FasterXML/jackson-datatype-joda and add it to your jackson configuration in ktor :
install(ContentNegotiation) {
jackson {
registerModule(JodaModule())
enable(SerializationFeature.INDENT_OUTPUT)
}
}
You can also control serialization/deserialization behavior with annotations on your data class properties :
data class Account(
val uid: String? = null,
val firstName: String,
val lastName: String,
val email: String,
#get:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
val createdTime: DateTime? = null
)

Can a field be cast to non null version of itself?

I have a data class
data class MyModel(private val _data: MyData? = null)
And I want to ensure my data is only accessible when it is not null, else throw.
I use the below which is good.
fun getData(): MyData {
return checkNotNull(_data) { "data shouldn't be null" }
}
However, if I follow the guide as per Override getter for Kotlin data class, the below complaints I need to return MyData? instead of MyData
val data = _data
get(): MyData {
return checkNotNull(field) { "data shouldn't be null" }
}
Is it true that field can't be cast to the Non-null version of it when return?
If your goal is to declare a getter for a Any? property that returns a Any, it's not possible. You'll get the following error:
Getter return type must be equal to the type of the property
So attempting to do something like
val test : String?
get() : String = "hi"
Wouldn't work.
However, you could hide the nullable property and expose a non-nullable property which references the nullable value via casting:
private val test : String? = "hi"
val testNotNull : String = test as String
If test referenced null, an exception will be thrown.
For example:
fun main(args: Array<String>) = print(Demo().testNotNull)
class Demo(private var test: String? = "hi") {
val testNotNull : String
. get() = test as String
}
You can test this snippit out at try.kotlin.org
Although this is not safe. You should rethink your design. If you're not interoping with Java, you shouldn't punish yourself with nullable types.
I don’t think you can. What you did with the fun getData() is a valid approach IMO. Or you could just not use a data class and create a normal class, obviously.
What I think it may work is with something like this:
typealias notNullType = MyData
data class Test(private val _value: MyData? = null) {
val v: notNullType = _value as notNullType
get() { return field }
}
This would totally allow you to do:
fun play() {
val t = Test(null)
print(t.v) //see what I did? :-)
}
THAT BEING SAID… I don’t think “hiding” the ? optional is necessarily a good idea.
It doesn't necessarily mean that the MyData class is null if you cast it like MyData?
The '?' Just allows the object to be null in the instance that it actually becomes null to avoid an exception at runtime.
You can make your class nullable and it can still contain your data.