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

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

Related

Kotlin Generic problem, UNCHECKED_CAST , required:Nothing

#file:Suppress("UNCHECKED_CAST")
data class Element<T>(
val key: String,
val valueOne: T,
val valueTwo: T,
val comparator: Comparator<T>,
val comparatorValue: CompareResult
)
enum class CompareResult(
val value: Int
) {
LESS(-1),
EQUAL(0),
GREATER_THAN(1)
}
fun <T> matchesComparison(list:Collection<Element<T>>): Pair<Boolean, List<String>> {
val failedComparisons = mutableListOf<String>()
for (element in list) {
val compareValue = element.comparator.compare(element.valueOne, element.valueTwo)
if (element.comparatorValue.value != compareValue) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
val elementsToCompare = listOf(
Element("number", 1, 2, intComparator, CompareResult.LESS),
Element("first name", "a", "a", stringComparator, CompareResult.EQUAL),
Element("last name", "a", "b", stringComparator, CompareResult.EQUAL)
)
matchesComparison(elementsToCompare).second.joinToString(", ","Failed elements: \"","\"")
I often get faced with comparing two different object properties with the same values.
As an example object A has props number,firstname,lastname. What i want to do is create a list have and have a function which goes over these Elements and returns which props have failed the comparison. I've managed to use generics for both the object and the matchesComparison function which returns the failed comparisons. The problem begins when i want to pass this list which is of type Collection<Element<out Any>> to this function is i get a type missmatch. instead of using unchecked casts to force the Comparator to be of type Any i would like to do this
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
The result value that of the script above should be Failed elements: "last name"
I tried changing the signature of the function to out any but then the comparator.compare method has both params as of type Nothing. I really want to avoid unsing unchecked casts.
matchesComparison() doesn't need to be generic in this case. It doesn't really care what is the type of the whole input collection, so we can simply use * here.
Then we have another problem. The compiler isn't smart enough to notice that while we perform operations on a single element, all its properties are of matching types. As a result, it doesn't allow to use element.comparator on element.valueOne and element.valueTwo. To fix this problem, we simply need to create a separate function which works on a single Element, so it understand the type for all properties is the same:
fun matchesComparison(list:Collection<Element<*>>): Pair<Boolean, List<String>> {
fun <T> Element<T>.matches() = comparatorValue.value == comparator.compare(valueOne, valueTwo)
val failedComparisons = mutableListOf<String>()
for (element in list) {
if (!element.matches()) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
Also, I believe such matches() function should be actually a member function of Element. It seems strange that while Element is pretty independent and it contains everything that is needed to perform a comparison, it still requires to use external code for this. If it would have a matches() function then we wouldn't need to care about its T. matches() would work with any Element.

Is there a way to lazy-init a costly field in a specific constructor?

I have a data class I want to populate, where in one constructor I have the data already, and in another I would like to fetch it only when it is required, which is rare.
A sample code would be:
data class Source1(val str1: String)
data class Source2(val str2: String)
data class DTO(val data1: String, val data2: String) {
// ctor which does not need laziness
constructor(source1: Source1) : this(
data1 = source1.str1,
data2 = source1.str1
)
// ctor which needs costly data
constructor(source2: Source2, costlyData: String) : this(
data1 = source2.str2,
data2 = costlyData
)
}
fun demo() {
val source1 = Source1("some str - 1")
DTO(source1)
val source2 = Source2("some str - 2")
val costlyData: String = costlyOperation() // this is the operation I'd like to execute lazily
DTO(source2, costlyData)
}
I'd say the easiest way would be to accept a function as a constructor parameter, something like this:
class DTO(provider:()->String){
constructor(data: String):this({data})
val data by lazy{ provider()}
}
So you can use it in both ways:
val eager = DTO("some str - 1")
val lazy = DTO(::costlyOperation)
A bit nicer way is to have a Source abstraction with different implementations for providing a constant value and performing an operation. But the overall idea would be the same.
Although I wouldn't call this DTO anymore and it loses its data class capabilities regarding the content.

Why can't Jackson deserialize this JSON?

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

How to group by Arraylist multidimensional

how do I group by "group" when located in the sub model?
i tried do this, but it error result
mtop.groupBy { it.sub[0].group }
this my code
// my model
data class mTop(val kategori : String, val sub : ArrayList<mSub>)
data class mSub(val id_menu : String, val nama_menu : String, val gambar : String, val group : String)
// my activity
val mtop: ArrayList<mTop> = ArrayList()
val msub: ArrayList<mSub> = ArrayList()
mtop.add(mTop(header, msub))
sorry I often ask on this forum but never helped others, because I'm a beginner and my English is bad. Thank You
mtop.groupBy { it.sub[0].group }
The error you are getting is because sub is empty and you are trying to get the first element.
What you can probably do is to do a filter before trying to groupBy:
mtop.filter{it.sub.isNotEmpty()}.groupBy{it.sub[0].group}

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