Why can't Jackson deserialize this JSON? - kotlin

I'm using Jackson to parse an ElasticSearch document into following data class
data class ElasticCity(
val id: Long,
val regionId: Long,
val countryIso: String,
val isFeatured: Boolean?
) {
// For now Jackson does not support this for constructor parameters https://github.com/FasterXML/jackson-databind/issues/562
#JsonAnySetter
val names: MutableMap<String, String> = mutableMapOf()
}
However I'm getting following error (formatting mine)
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException:
Instantiation of [simple type, class net.goout.locations.model.ElasticCity] value failed
for JSON property country_iso due to missing (therefore NULL) value for creator parameter countryIso which is a non-nullable type
at [Source: (byte[])
"{
"name.cs":"Brno",
"countryIso":"CZ",
"regionId":85682423,
"timezone":"Europe/Prague",
"name.de":"BrĂ¼nn",
"name.sk":"Brno",
"id":101748109,
"isFeatured":true,
"name.pl":"Brno",
"name.en":"Brno"
}";
line: 1, column: 186] (through reference chain: net.goout.locations.model.ElasticCity["country_iso"])
Clearly the key countryIso is present in the JSON but for some reason Jackson complains a key country_iso is missing. Why? How can I fix this?

try adding
data class ElasticCity(
val id: Long,
val regionId: Long,
#JsonProperty(value = "countryIso") val countryIso: String,
val isFeatured: Boolean?
)
Jackson mapper implicitly converts non start caps characters _
If you want to fix this at multiple places then take a look at
#JsonNaming(PropertyNamingStrategy..
https://www.programcreek.com/java-api-examples/?api=com.fasterxml.jackson.databind.PropertyNamingStrategy

Related

TypeVariable(V) Required in Mutable Map assignment in Kotlin. Cannot put the value from a map to itself

The following example is a simplified version of what I am trying to do in my application.
fun main() {
val test: MutableMap<Int, String> = mutableMapOf(
1 to "Apple",
)
test[2] = test[1] // test[1] has incorrect type.
}
The code doesn't compile. IntelliJ gives the following hint:
Type mismatch.
Required: TypeVariable(V)
Found: String?
I don't understand what a TypeVariable is. but when I provide a default value the error disappears
test[2] = test[1] ?: "Grape"
Why the required type is TypeVariable(V), not String, and what exactly is it? What to do if there's no default value for my application purposes?
... = test[1]
returns a String? as the hint showed you. But test is a MutableMap of <Int, String>, which means you need to assign a String:
... = test[1]!!
Of course this will only work if 1 is a valid key in test. Otherwise your code with the null safety operator is the way to go:
... = test[1] ?: "default value"

Kotlin - creating map with 3 arrays using fold not working

I have an array of customers, each customer has properties id, uuid and subCustomer and other properties that I am not interested in. I would like to do one iteration, where I would create 3 arrays where one would hold ids, other uuids and third subcustomers only.
I have tried to achieve this by using fold function like this:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
acc["ids"]?.plus(customer["id"])
acc["uuids"]?.plus(customer["uuid"])
acc["subCustomers"]?.plus(customer["subCustomer"])
}
With this code I get an error in editor:
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
List<Any?>?
I have tried this as well:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer["id"]),
"uuids" to acc["uuids"]?.plus(customer["uuid"]),
"subCustomers" to acc["subCustomers"]?.plus(customer["subCustomer"])
)
}
But, I get this errors:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<Any?>?
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
Map<String, List<Any?>?>
Write two data classes for your data. One for your customers, and one for the three lists that you want:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class CustomerDataLists(
val ids: MutableList<String> = mutableListOf(),
val uuids: MutableList<UUID> = mutableListOf(),
val subCustomers: MutableList<String> = mutableListOf(),
)
Then, just use a simple for loop to add the data in:
val dataLists = CustomerDataLists()
for (customer in customers) {
dataLists.ids.add(customer.id)
dataLists.uuids.add(customer.uuid)
dataLists.subCustomers.add(customer.subCustomer)
}
// now dataLists is filled with customers' data
#Sweeper's answer is nice. I believe in any case it's worth using data classes instead of maps for this kind of use case.
Since you don't really have any interactions between the 3 lists in the fold, you could also build those lists independently (but it's 3 iterations of course here):
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class AggregatedCustomers(
val ids: List<String>,
val uuids: List<UUID>,
val subCustomers: List<String>,
)
val customers: List<Customer> = TODO("get that list from somewhere")
val aggregated = AggregatedCustomers(
ids = customers.map { it.id }
uuids = customers.map { it.uuid }
subCustomers = customers.map { it.subCustomer }
)
This answer contiains 3 parts:
A better way to solve such problem;
Why the original code doesn't work;
Other problems need to pay attention.
1. A better way to solve such problem
Let's assume that the Consumer mentioned looks like this:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
It's really not necessary to use function fold in such occasion. For loop or extension function forEach is merely enough:
val customers: List<Customer> = listOf(
Customer("1", UUID.randomUUID(), "sub-1"),
Customer("2", UUID.randomUUID(), "sub-2"),
Customer("3", UUID.randomUUID(), "sub-3"),
)
val ids = mutableListOf<String>() // pay attention. use `mutableListOf` instead of `listOf()`
val uuids = mutableListOf<UUID>()
val subConsumers = mutableListOf<String>()
customers.forEach {
ids += it.id
uuids += it.uuid
subConsumers += it.subCustomer
}
2. Why the original code doesn't work
The proposed two pieces of code are in the same pattern:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
) { acc, customer ->
// ... do something with acc and customer
}
We should first make it clear that the last statement in the fold scope is the expression to be accumulated. It's like an acc_n <combine> customer -> acc_(n+1), for each customer in customers each time, where <combine> is where we write our logic. So the first proposed piece of code doesn't work because you might not be aware that something should be returned while writing:
customers.fold(...){ acc, customer ->
acc["ids"]?.plus(customer.id)
acc["uuids"]?.plus(customer.uuid)
acc["subCustomers"]?.plus(customer.subCustomer)
}
In fact, the last statement acc["subCustomers"]?.plus(...) is an expression with type List<Any>?, kotlin regard it as your "acc_(n+1)", but you propose mapOf("ids" to ...) as acc_0, which has type Map<String, ...>: not the same type as List<Any>?. And that's why you got the first error:
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
List<{Comparable{String & UUID}> & java.io.Serializable}>?
We'll talk about generic types later.
Let's move on the second piece of code. A map is proposed as the last expression in the scope of fold, which is also a map:
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id),
"uuids" to acc["uuids"]?.plus(customer.uuid),
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)
)
}
The simpliest way to eliminate error is using !! expression (not suggested!):
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id)!!,
"uuids" to acc["uuids"]?.plus(customer.uuid)!!,
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)!!
)
}
The reason is that kotlin cannot assert acc["ids"] is not null, that's why you use ?. for a null-safe method invoke. However such invoke make the return type nullable:
val cus: Customer? = Customer("1", UUID.randomUUID(), "sub-1") // cus has type Customer? : nullable
val id1: String = cus?.id // [compile error] Type mismatch. [Required: String] [Found: String?]
val id2: String? = cus?.id // OK
val id3: String = cus?.id!! // If `cus?.id` is null, throw NPE.
You've declare acc_0 (in bracket after fold) in type Map<String, List<T>> implicitly (we will talk about T later). Just know that T is not a nullable type), but a map with type Map<String, List<T>?> was found as acc_(n+1). Types mismatch and the error was shown:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<{Comparable{String & UUID}> & java.io.Serializable}>?
3. Other problem need to pay attention
An important problem is: What's the type of acc_0?
// acc_0:
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
Of course type of each expression on the left of to is String, and List<T> is the type of each expression on the right of it. so it must be Map<String, List<T>>. What about T? Kotlin try to find the nearest ancessor of String and UUID, and find them both implements Comparable<?> and Serializable, so that's what you see in the error. That's the type of T:
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
This may lead to some unwanted experience:
val map = mapOf(
"listA" to mutableListOf("233"),
"listB" to mutableListOf(UUID.randomUUID())
)
val listA = map["A"]!! // MutableList<out {Comparable{String & UUID}> & java.io.Serializable}!>
// generic type "collapse" into `Nothing` for no type can implement both Comparable<String> and Comparable<UUID>
listA.add(Any()) // Type mismatch. [Required: Nothing] [Found: Any]
So try not to put lists with different generic type into one map.
Another problem is, when you try to invoke acc["ids"]?.plus(customer.id), you are actually invoking such method (from kotlin _Collections.kt)
public operator fun <T> Collection<T>.plus(element: T): List<T> {
val result = ArrayList<T>(size + 1)
result.addAll(this)
result.add(element)
return result
}
A new list is created each time you invoke the method! Try use mutableListOf() in replace of listOf() for collections that you want to make changes, and use "+=" (or ?.plusAsign() as null-safe version) operator instead. This may leads to some other problem with the original code (which is too complex to explain why), but for the code in part 1: A better way to solve such problem, the += is actually invoking:
public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) {
this.add(element)
}
which just add value to list without create new ones.

Can I specify an object with #SerializedName in its params as a #Field in a url-encoded POST request?

Take the following JSON i want to send to an API:
{
"param1": "1",
"param2": "2",
"param3": {
"param3-1": "3-1",
"param3-2": "3-2"
}
}
And my object for param3 is:
data class param3Object(
#SerializedName("param3-1") param3_1: String,
#SerializedName("param3-2") param3_2: String
)
Is it possible to pass the above object into an API request #Field attribute:
#FormUrlEncoded
#POST("api/something")
fun doSomething(
#Field("param1") param1: String,
#Field("param2") param2: String,
#Field("param3") param3: param3Object
): Call<MyResponse>
so that it represents the JSON at the top of this post? Or do I need to individually pass parameters as #Field? I'm trying to think of ways not to waste time boilerplating.
You should rather use it as REQUEST BODY
#POST("api/something")
fun createUser(#Body data: Data) : Call<MyResponse>
and define model
data class Data(
#SerializedName("param1") val param1: String,
#SerializedName("param2") val param2: String,
#SerializedName("param3") val param3: SubData
)
data class SubData(
#SerializedName("param3-1") val param3_1: String,
#SerializedName("param3-2") val param3_2: String
)
EDIT
If you really want send this as formUrlEncoded, then probably simple solution is to change parameter3 to string, and serialize data before put it to method, or even better change everything to single parameter and serialize whole object

Kotlin ArrayList won't append object using 'add' method

When trying to 'add' a custom object to an ArrayList, the ArrayList remains null
I've tried modifying the data class initialization by using MutableLists, among other things
Here is the data class that I'm using:
data class WYRStatistics(val team: String, val league: String, val gp: String, val g: String, val a: String, val pt: String, val pim: String, val pm: String)
This is where I'm using the data class to construct on object and try to add the object to an ArrayList
var n = 0
var statsArr: ArrayList<WYRStatistics>? = null
while (n < tempStats.length()) {
val statObject = tempStats.getJSONObject(n)
val singleStat = WYRStatistics(statObject.getString("Team"),
statObject.getString("League"),
statObject.getString("GamesPlayed"),
statObject.getString("Goals"),
statObject.getString("Assists"),
statObject.getString("Points"),
statObject.getString("PenaltyMinutes"),
statObject.getString("PlusMinus")
)
println(singleStat)
statsArr?.add(singleStat)
println(statsArr)
tempPlayer.stats?.add(singleStat)
println(tempPlayer)
n++
}
The utilization is inside of a function that handles the asynchronous task of decoding JSON. This is all working fine.
tempStats is a JSONArray - when printed it is formatted properly
When I println singleStat, the object is printed properly
However, when I 'add' singleStat to statsArr, the statsArr returns null
You are initializing arrayList to nullas what JB mentioned.
The correct way should be
val statsArr = arrayListOf<WYRStatistics>()

get a list of parsed json elements

I parsed a json string to the following object structure using gson:
data class Base (
val expand: String,
val startAt: Long,
val maxResults: Long,
val total: Long,
val issues: List<Issue>
)
data class Issue (
val expand: String,
val id: String,
val self: String,
val key: String,
val fields: Fields
)
data class Fields (
val summary: String,
val issuetype: Issuetype,
val customfield10006: Long? = null,
val created: String,
val customfield11201: String? = null,
val status: Status,
val customfield10002: Customfield10002? = null,
val customfield10003: String? = null
)
Everything works fine and also the object model is correct, because I can access each element of the object.
However, I encountered the problem that I dont know how to get a list of all field-elements. Right now, I have only figured out how to access one item (by using an index and get()-function):
val baseObject = gson.fromJson(response, Base::class.java)
val fieldsList = baseObject.issues.get(0).fields
I actually want to have a list of all field elements and not just one. Is there a gson function allowing me to do that? I couldn't find anything about it in the gson documentation for java.
You don't have to look for some gson function when you've already created a baseObject. You just need to get from each issue it's fields and you can use a map function to achieve this, it will convert each issue to a new type so you can get issue fields there
val fieldFromAllIssues: List<Fields> = baseObject.issues.map { it.fields }
it in this context is a one issue. More explanation about it is here