I have the following Kotlin data class:
data class TestObject(
val boolField: Boolean,
val stringField: String,
val nullBoolField: Boolean = true,
val nullStringField: String = "default",
val notThereBoolField: Boolean = true,
val notThereStringField: String = "not there"
)
I am then attempting to deserialize some JSON into this class using Jackson v2.9.4 with the Jackson Kotlin plugin v2.9.4.1. The test JSON is as follows:
{
"boolField": true,
"stringField": "string",
"nullBoolField": null,
"nullStringField": null
}
The first two and the last two fields deserialize successfully - with the values from the JSON and the default values from the Kotlin data class, as appropriate. However, when the middle pair of fields are present, the deserialization fails with:
Instantiation of [simple type, class com.example.TestObject] value
failed for JSON property nullStringField due to missing (therefore
NULL) value for creator parameter nullStringField which is a
non-nullable type
I know I could solve the problem by changing the types of nullBoolField and nullStringField to Boolean? and String? respectively, but since default values are known I would rather not have to push the handling of null further down into the code by doing that.
The question is: is there any way of configuring Jackson to use the Kotlin data class default values for non-nullable properties when the property is present in the JSON but set to null?
You could try first to filter null values from json and after to deserialize it to Kotlin object.
Or you may to try add feature to kotlin-jackson module, with adding a new feature parameter, which will enable a null ignoring from json parameters and use default values.
You may do this by modify this line (if I'm not mistaken)
If you don't have any value and field is non-nullable then you should not pass it in request body:
{
"boolField": true,
"stringField": "string"
}
This results in following object, as expected:
{
"boolField": true,
"stringField": "string",
"nullBoolField": true,
"nullStringField": "default",
"notThereBoolField": true,
"notThereStringField": "not there"
}
Related
Assuming I have the following Kotlin class:
data class Foo(val bar: String, val dates: List<LocalDate>)
How do I configure JacksonMapper to deserialize
{
"bar": "bar-value"
}
to Foo instance with dates set to an emptyList()?
I was playing with:
enable(ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) (it has different purpose)
secondary constructor with nullable dates (failed due to JVM signature clash)
the default value for dates i.e. val dates: List<LocalDate> = emptyList()
custom deserializer (wasn't invoked)
but without luck.
adding
#field:JsonSetter(nulls = Nulls.AS_EMPTY)
did the trick :)
However it would be great if I could set it globally.
I have a data class that has the following form:
data class ContentElementField(val type: String) {
val text: String? = null
get() = requireNotNull(field)
val style: String? = null
get() = requireNotNull(field)
val path: String? = null
get() = requireNotNull(field)
val caption: String? = null
get() = requireNotNull(field)
}
The problem arises when I want to perform the following operation:
when (it.type) {
"text" -> TextElement(Text(it.text), Style(it.style))
"image" -> ImageElement(Path(it.path), Caption(it.caption))
}
The compiler warns me about that You cannot send a nullable type to a function that does not accept nullable arguments.
Even if the field is signed to be nullable, its getter is signed to be not nullable, though.
The compiler should use getters to resolve whether to give this warning.
What would you offer to get around this problem?
It doesn't matter if your getter happens to crash if the current value is null - the type is still nullable, the getter's return type is still String?.
Why are you doing this anyway? Why not just make the fields non-null as normal and let a null assignment throw the exception instead? That way you won't have to fight the type system.
If what you have in mind is different and this was just meant to be a simple example, then you have a few options:
Use !! at the call site since you're guaranteeing it's not null
"text" -> TextElement(Text(it.text!!), Style(it.style))
Expose the private nullable property through a non-null one:
// I see people do this a lot in Activities and Fragments even though
// they should probably just be making the one property lateinit instead
private val _text: String? = whatever
val text: String get() = requireNotNull(_text)
Maybe look at Kotlin contracts which allow you to make guarantees to the compiler about values (no example because I've never used it)
It's not really clear what you actually want to do though, or why this is useful. Your example is even using vals and assigning null to them. Whatever your real use case is, there's probably a better way.
(Also in case you're not aware, properties that aren't constructor parameters aren't included in the basic data class behaviour, i.e. its equals/hashCode/toString implementations. Another reason just making the types non-null helps, you can stick them in the constructor instead of having to do this logic)
I have a data https://gist.githubusercontent.com/iva-nova-e-katerina/fc1067e971c71a73a0b525a21b336694/raw/954477261bb5ac2f52cee07a8bc45a2a27de1a8c/data2.json a List with seven CheckResultItem elements.
I trying to parse them this way:
import com.fasterxml.jackson.module.kotlin.readValue
...
val res = restHelper.objectMapper.readValue<List<CheckResultItem>>(text)
which gives me the following error:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.fmetric.validation.api.Brick] value failed for JSON property upperLevelBricks due to missing (therefore NULL) value for creator parameter upperLevelBricks which is a non-nullable type
at [Source: (StringReader); line: 1, column: 714] (through reference chain: java.util.ArrayList[0]->com.fmetric.validation.api.checking.CheckResultItem["brick"]->com.fmetric.validation.api.Brick["upperLevelBricks"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:116)
There is #JsonIgnore annotation in data class :
data class Brick(
val id: UUID?,
val name: String,
val type: BrickType,
val propertyValues: List<ProjectBrickPropertyValue<*>>,
#JsonIgnore
val upperLevelBricks: ArrayList<Brick>,
val downLevelBricks: ArrayList<Brick>,
var drawingDetails: List<BrickDrawingDetails>?
) {
But it seems it doesn't work. Could you explain me what is wrong?
UPD: Also I have tried #JsonIgnoreProperties({"upperLevelBricks"}) class annotation but it doesn't work. My solution was to set a default value
val upperLevelBricks: ArrayList<Brick> = arrayListOf(),
But I think that annotations should work!
Actually, it works, but not the way you think. During deserialization #JsonIgnore ignores the respectful field in JSON, like it wasn't there (but it's doesn't make sense in this case, because it's initially absent in JSON).
In Java, Jackson would've just instantiated class with null value for the absent field (because all object types in Java are nullable, which means they allow the value to be set to null). But in Kotlin, a property should be explicitly marked as nullable (val upperLevelBricks: List<Brick>?) or have a default value (val upperLevelBricks: List<Brick> = emptyList()) so that Jackson could create a class instance in this case.
Note that approach with default value for property won't work (unless you additionally mark it with #JsonIgnore) if this field is present in JSON but explicitly set to null:
{
...
"upperLevelBricks": null,
...
}
Anyway, if you don't want to change the API of your Brick class you may provide a default value for this field only when it's created during Jackson deserialization (and only if it's absent/null in JSON) via custom deserializer:
object EmptyListAsDefault : JsonDeserializer<List<Brick>>() {
override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): List<Brick> =
jsonParser.codec.readValue(
jsonParser,
context.typeFactory.constructCollectionType(List::class.java, Brick::class.java)
)
override fun getNullValue(context: DeserializationContext): List<Brick> = emptyList()
}
data class Brick(
//...
#JsonDeserialize(using = EmptyListAsDefault::class)
val upperLevelBricks: List<Brick>,
//...
)
I'm trying to create new rule for detekt project. To do this I have to know exact type of Kotlin property. For example, val x: Int has type Int.
Unfortunately, for property of type private val a = 3 I receive the following:
property.typeReference is null
property.typeParameters is empty
property.typeConstraints is empty
property.typeParameterList is empty
property.text is private val a = 3
property.node.children().joinToString() has object notation of previous item
property.delegate is null
property.getType(bindingContext) is null (the property bindingContext is part of KtTreeVisitorVoid used
Question: how can I get name of type (or, what is better, object KClass) to compare the actual property type with Boolean class type? (e.g. I just need to get if property boolean of not)
Code:
override fun visitProperty(property: org.jetbrains.kotlin.psi.KtProperty) {
val type: String = ??? //property.typeReference?.text - doesn't work
if(property.identifierName().startsWith("is") && type != "Boolean") {
report(CodeSmell(
issue,
Entity.from(property),
message = "Non-boolean properties shouldn't start with 'is' prefix. Actual type: $type")
)
}
}
Right solution:
fun getTypeName(parameter: KtCallableDeclaration): String? {
return parameter.createTypeBindingForReturnType(bindingContext)
?.type
?.getJetTypeFqName(false)
}
There are at least following values for Boolean type: kotlin.Boolean and java.lang.Boolean
Full code is here.
I have below AuthenticationResponse model that I use for 3 api calls using retrofit2. If I make the verifyEmail call f.e. the JSON response body only contains an attribute for valid email (so something like {"validEmail": true} ). The other 2 calls only contain attributes for "resetSuccesful" or the other 4.
How can I make sure/check that when I receive the response to the verifyEmail call f.e. that it contains a non null value for validEmail??
Service:
interface AuthenticationService {
#POST("auth/checkEmail")
fun verifyEmail(#Body email: String): Call<AuthenticationResponse>
#POST("auth/login")
fun login(#Body loginCredentials: LoginCredentials): Call<AuthenticationResponse>
#POST("auth/resetPassword")
fun resetPassword(#Body email: String): Call<AuthenticationResponse>
}
Model:
data class AuthenticationResponse(
val validEmail: Boolean? = null,
val loginSuccess: Boolean? = null,
val retriesLeft: Int? = null,
val authToken: String? = null,
val accountBlocked: Boolean? = null,
val resetSuccesful: Boolean? = null)
edit:
If i mock my server response to return f.e. responseCode = 200 - { "validEmail": null } and change validEmail type to Boolean (instead of Boolean?) Retrofit doesn't thrown any kind of exception (this is what i actually want) thus my model is giving me a false negative for my validEmail value..
You should definitely consider #miensol's comment -- to have separate model objects for different API calls.
However, if that's not possible, you can use Sealed class.
sealed class AuthenticationResponse {
data class EmailValidation(val validEmail: Boolean) : AuthenticationResponse()
data class SomeSecondResponse(val loginSuccess: Boolean, ...) : AuthenticationResponse()
data class SomeThirdResponse(val resetSuccessful: Boolean) : AuthenticationResponse()
}
fun handleResponse(response: AuthenticationResponse) {
when (response) {
is AuthenticationResponse.EmailValidation -> response.validEmail
is AuthenticationResponse.SomeSecondResponse -> response.loginSuccess
is AuthenticationResponse.SomeThirdResponse -> response.resetSuccessful
}
}
Sealed class is enums on steroids -- it is enums with states. You have to create 3 classes for 3 responses which inherit from the sealed class AuthenticationResponse.
You have to create the specific class instance corresponding to the different API calls. To access the data, you can do type check and access the specific data. The above when example shows how to access all the types of response inside a function.
How can I make sure/check that when I receive the response to the
verifyEmail call f.e. that it contains a non null value for
validEmail??
Since you create the instance of only the specific classes and all the classes have only non-null properties, you don't have to worry about null.
I would consider what #miensol mentioned in a comment, but if you wanted to add a check for this, you could do something like:
fun isEmailValid(authResponse: AuthenticationResponse): Boolean {
return authResponse.validEmail ?: false
}
See Kotlin docs on Elvis Operator:
If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.