Custom (Jackson) BigDecimal Deserialization - "override behaviour" - kotlin

I am using Jackson for deserialaizing JSON. We are also using BigDecimal type to deserialize an incoming String into and this value can contain comma's, e.g. ten thousand can be submitted as "10,000". To be able to deserilaize this value into BigDeciaml, I have created a simple custom class:
class BigDecimalCommaDeserializer : JsonDeserializer<BigDecimal>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BigDecimal? {
return if (parser.text != null && parser.text.isNotEmpty())
BigDecimal(parser.text.replace(",", ""))
else null
}
}
and wired up as follows:
#JsonDeserialize(using = BigDecimalCommaDeserializer::class)
#JsonFormat(shape = JsonFormat.Shape.STRING)
var unitCount: BigDecimal? = null,
I notice though, there is also a com.fasterxml.jackson.databind.deser.std.BigDecimalDeserializer. Will this still be called, or should i think about delegating a call to it from my BigDecimalCommaDeserializer?

Custom deserialiser, BigDecimalCommaDeserializer, will be used in that case. Common one, BigDecimalDeserializer, will be used by default in all other places where custom deserialiser is not specified.

Related

Strange kotlin checkNotNullParameter error

we received a crash on Firebase for a kotlin method:
Fatal Exception: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter code
at [redacted].DeliveryMethod.<init>(:2)
at [redacted].DeliveryMethodsUpdater$addSingleDMInAd$clientCall$1.invokeSuspend(DeliveryMethodsUpdater.kt:121)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
the model is this one:
class DeliveryMethod() {
lateinit var code: String
lateinit var name: String
lateinit var description: String
var isAddressRequired: Boolean? = null
var image: JSONObject? = null
var isDefault: Boolean = false
constructor(code: String) : this() {
this.code = code
}
constructor(code: String, name: String, description: String, image: JSONObject? = null) : this() {
this.code = code
this.name = name
this.description = description
this.image = image
}
}
and the method:
private suspend fun addSingleDMInAd(
adId: Long,
deliveryMethodCode: String
): JoinAdDeliveryMethod? {
var addedDeliveryMethod: JoinAdDeliveryMethod? = null
val clientCall = GlobalScope.async(Dispatchers.IO) {
val cd = CountDownLatch(1)
Client.getInstance().addDeliveryMethodInAd(
adId,
DeliveryMethod(deliveryMethodCode),
object : NetworkCallback<JoinAdDeliveryMethod> {
override fun onSuccess(result: JoinAdDeliveryMethod) {
addedDeliveryMethod = result
cd.countDown()
}
override fun onFailure(err: NetworkError?) {
addedDeliveryMethod = null
cd.countDown()
}
}
)
cd.await()
}
clientCall.await()
return addedDeliveryMethod
}
now, I understand that that the constructor for DeliveryMethod is being called with a null value for code, but I don't understand why the exception only come up at this point. As you can see, the method param is also marked as non-null, and so are previous methods. Shouldn't the exception be thrown way before getting to the constructor call for DeliveryMethod?
EDIT:
this is the caller of addSingleDMinAd():
fun addDeliveryMethodsInAd(
adId: Long,
deliveryMethodCodesToAdd: List<String>,
completionListener: (List<JoinAdDeliveryMethod?>) -> Unit
) {
GlobalScope.launch {
val updatedDms: MutableList<JoinAdDeliveryMethod?> = mutableListOf()
for (deliveryCode in deliveryMethodCodesToAdd) {
addSingleDMInAd(adId = adId, deliveryMethodCode = deliveryCode).run {
updatedDms.add(this)
}
}
completionListener.invoke(updatedDms)
}
}
this is the java caller of the addDeliveryMethodsInAd() (this is inside an Android Service):
new DeliveryMethodsUpdater().addDeliveryMethodsInAd(
result.getId(),
deliveryMethodCodesToAdd,
updatedDMs -> {
// on failed delivery method request
for (JoinAdDeliveryMethod updatedDm : updatedDMs) {
if (updatedDm == null) {
//show error
break;
}
}
AdDetailUpdater
.getInstance()
.updateSubscribersWithDeliveryMethods(result.getId(), updatedDMs);
return null;
}
);
Shouldn't the exception be thrown way before getting to the constructor call for DeliveryMethod?
Within Kotlin, it's not possible for a non-null parameter to be given a null value at runtime accidentally (because the code wouldn't have compiled in the first place). However, this can happen if the value is passed from Java. This is why the Kotlin compiler tries to protect you from Java's null unsafety by generating null-checks at the beginning of some methods (with the intrinsic checkNotNullParameter you're seeing fail here).
However, there is no point in doing that in private or suspend methods since they can only be called from Kotlin (usually), and it would add some overhead that might not be acceptable in performance-sensitive code. That is why these checks are only generated for non-suspend public/protected/internal methods (because their goal is to prevent misuse from Java).
This is why, if you manage to call addSingleDMInAd with a null argument, it doesn't fail with this error. That said, it would be interesting to see how you're getting the null here, because usually the checks at the public API surface are enough. Is some reflection or unsafe cast involved here?
EDIT: with the addition of the calling code, this clears up the problem. You're calling a method that takes a List<String> from Java, with a list that contains nulls. Unfortunately Kotlin only checks the parameters themselves (in this case, it checks that the list itself is not null), it doesn't iterate your list to check for nulls inside. This is why it didn't fail at the public API surface in this case.
Also, the way your model is setup is quite strange. It seems the lateinit is lying because depending on which constructor is used, the properties may actually not be set at all. It would be safer to mark them as nullable to account for when users of that class don't set the value of these properties. Doing this, you won't even need all secondary constructors, and you can just use default values:
class DeliveryMethod() {
var code: String? = null,
var name: String? = null,
var description: String? = null,
var image: JSONObject? = null,
) {
var isAddressRequired: Boolean? = null
var isDefault: Boolean = false
}
Other things of note about addSingleDMInAd:
don't use GlobalScope in this case. If you need to run short-lived coroutines, provide them with a smaller scope that is cancelled when the work is not needed anymore - it ensures no coroutines are leaked. You can read more about the potential pitfalls of GlobalScope and possible replacements in its own doc. That said, you probably shouldn't be starting a coroutine at all anyway here, see next point.
don't use async {} if you use await() right after - it's pointless to start something asynchronous if you wait for it right there. If you want to switch the context to IO, use withContext(Dispatchers.IO) { ... } instead. That said, you shouldn't even need to use the IO dispatcher here, see next point.
don't use CountDownLatch for this purpose. The proper way to encapsulate an async API as a suspend function for coroutines is to use suspendCancellableCoroutine (check its doc, it provides an example on how to use it). Once you use this, there is no need anymore for Dispatchers.IO because it will not be blocking the current thread anymore.

Why #JsonIgnore annotation doesn't work during deserializing data?

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>,
//...
)

Strange Kotlin behaviour using Data Classes in Maps

I'm new to Kotlin and I'm trying to understand it, I've just written a simple example that shows how using data classes with maps is a bit tricky, because it seems to me that data classes have a strange behaviour. By default, they define hashCode() based on every property of the class. But they don't define a default equals() method.
This caused to me a lot of confusion because I created a HashMap with a Data Class as a key, but I didn't override hashCode() and equals(). My data class has a MutableList member. When I put an element in the map, I retrieved it using map.get(dataObject) as long as I didn't add an element to the MutableList. After that, even if the data object was still the same, and I found it using map.keys (map.keys.indexOf(dataObject) works), map.get(dataObject) failed, due to the hashCode().
I can fix it using a normal class or adding hashCode() and equals(), removing the MutableList from hashCode(), but I'm wondering if, due to the default behaviour, overriding hashCode() and equals() should be "mandatory" with data classes because otherwise using them with Maps can lead to errors.
Is there something else I can do to avoid this problem?
package cards
data class Player(val name: String, var cards: MutableList<Card>) {
constructor(name: String): this(name, mutableListOf())
//I don't need to define equals, so pointers are checked. But if I don't override hashCode, as it's based
//on every property, the hashCode is calculated considering the content of the MutableList!
// override fun hashCode(): Int {
// return name.hashCode()
// }
}
data class Card(val name: String, val suite: String)
class Game(val players: List<Player>) {
val cardMap: MutableMap<Player, MutableList<Card>> = mutableMapOf()
fun putIntoMapAndGiveCards() {
val newCards = cardMap.getOrDefault(players[0], mutableListOf())
newCards.add(Card(name = "Four", suite = "Clubs"))
cardMap[players[0]] = newCards
//This changes the default hashCode - I can use data classes in a list, but not in a map, because maps are
//based on it.
players[0].cards.add(Card(name = "Five", suite = "Clubs"))
}
fun getFromMap(): MutableList<Card>? {
val player = players[0]
assert(player != null, { "Player from list failure" })
val indexOfPlayer = cardMap.keys.indexOf(player)
assert(indexOfPlayer == 0, { "Player is in the map" })
//Without overriding hashCode, cards is null!
val cards = cardMap.get(players[0])
assert(cards != null, { "Cards from map failure" })
return cards
}
}
fun main() {
val player1 = Player(name = "John")
val game = Game(mutableListOf(player1))
game.putIntoMapAndGiveCards()
game.getFromMap()
?: throw Exception( """Map.get() failure because Player is a data class.
| A data class by default builds its hashCode with every property. As it contains a MutableList,
| the hashCode changes when I add elements to the list. This means that I can't find the element using get()
""".trimMargin())
println("Test finished!")
}
By default, they define hashCode() based on every property of the class. But they don't define a default equals() method
This is not correct. Data classes generate both equals() and hashCode() consistently based on the properties declared in the data class's primary constructor (same goes for toString() btw).
Here is the decompiled code for equals and hashCode of your Player class:
public int hashCode() {
String var10000 = this.name;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
List var10001 = this.cards;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Player) {
Player var2 = (Player)var1;
if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.cards, var2.cards)) {
return true;
}
}
return false;
} else {
return true;
}
}
Your problem is that you declare your cards mutable list in the primary constructor so it's part of the generated equals and hashCode.
The solution is to move this cards property to the body of your class instead (since it's not part of the player's "core data", but rather part of the state):
data class Player(val name: String) {
val cards: MutableList<Card> = mutableListOf()
}
This way, the generated equals/hashCode pair will only be based on the name property.
Another option obviously is to override both equals and hashCode manually to take only the name into account, but that's tedious and not very idiomatic.
I'm wondering if, due to the default behaviour, overriding hashCode() and equals() should be "mandatory" with data classes because otherwise using them with Maps can lead to errors.
I think you have misdiagnosed the default behaviour. So I'd say on the contrary overriding equals/hashCode is actually not very idiomatic for data classes, and should in general be avoided.
Using data classes is usually safe in maps, as long as the data in the primary constructor is not mutable.
Side notes
you really should not mix var with mutable collections. It creates 2 ways of changing the collection, which is pretty unexpected and error-prone. You should instead either use a val MutableList or a var List, so you can only change the list via mutation, or only change it via assignment, but not both.
if you want to insert the new value into the map, you shouldn't use getOrDefault + assign the value to the key. Instead, use getOrPut directly, so the default value will be inserted without extra work.
why are you both using a cards property on the Player and a Map<Player, List<Card>>? Looks like you have 2 states that can change independently now because those card lists are independent.

Non nullable field of object created via reflection is null

I have next class structure:
class ExampleResponse(
#SerializedName("status")
val status: String
)
I am creating instance of this class via reflection:
fun convert(typeAdapterFactory: TypeAdapterFactory, gson: Gson): Optional<*> {
return try {
val genericTypeClassName = "com.example.package.ExampleResponse"
val genericClass = Class.forName(genericTypeClassName)
val genericTypeAdapter = gson.getDelegateAdapter(typeAdapterFactory, TypeToken.get(genericClass))
val response = genericTypeAdapter.fromJson("{}")
Optional.of(response)
} catch (e: Exception) {
Optional.empty<Any>()
}
}
I am waiting that on the line genericTypeAdapter.fromJson("{}") it will throw exception, because status will be null. But I am receiving instance of ExampleResponse with null status field.
I want to return Optional.empty() if status is null. How can I achieve this without checking fields? (checking field to non null is not acceptable because the real function is universal and I won't know what class I'll receive here).
Gson doesn't know when a field can/cannot be null, since it's a Java library and Java knows nothing about nullability.
This question and answers suggests some workarounds:
Gson optional and required fields

Retrofit API call: How to make sure that value is not null after making api call?

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.