What is the easiest way to print a Kotlin data class as compilable code? - kotlin

I'd like to be able to turn an instance of a fairly simple Kotlin data class into a String that could be copy and pasted into a Kotlin file and would compile.
For example, given these data classes:
data class Parent(val name: String, val age: Int, val children: Set<Child>)
data class Child(val name: String, val age: Int)
I would like a function from any data class to String such that:
toCompilableString(
Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))
)
would return
"""Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))"""
Does such a thing exist?

We can override the behaviour of toString to output in the desired format:
fun main() {
var amy = Child(name="Amy",age=4)
var bob = Child(name="Bob",age=7)
var joe = Parent(name="Joe", age=34, children=setOf(amy, bob))
print(joe) // outputs "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))"
}
data class Parent(val name: String, val age: Int, val children: Set<Child>) {
override fun toString() = "Parent(\"$name\", $age, setOf(${children.joinToString()})"
}
data class Child(val name: String, val age: Int) {
override fun toString() = "Child(\"$name\", $age)"
}
With the help of joinToString(), this will output in the format "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))".

If you really love pain, there are reflection tools made specially for such things. I made a small function that will generate what you need:
fun dataClassToString(instance: Any) {
val sb = StringBuilder()
sb.append("data class ${instance::class.qualifiedName} (")
var prefix = ""
instance::class.memberProperties.forEach{
sb.append(prefix)
prefix = ","
sb.append("${it.name} = ${it.getter.call(instance)}")
}
sb.append(")")
println(sb.toString())
}
The only problem with this function is that for your parent class it generates the following:
data class Parent (age = 34,children = [Child(name=Amy, age=4), Child(name=Bob, age=7)],name = Joe)
Internally set is represented as array, however if you know for sure that you will have only sets or arrays, you can easily check what type it is and append that type when creating the set. You can also check if this is a data class and append it instead of hardcoded string.

Related

kotlin data class constructors not getting picked up

I am creating a data class in kotlin as such
data class User(val name: String, val age: Int)
{
constructor(name: String, age: Int, size: String): this(name, age) {
}
}
In my main function, I can access the objects as such:
fun main(){
val x = User("foo", 5, "M")
println(x.name)
println(x.age)
println(x.size) // does not work
}
My problem is that I can't get access to size.
What I am trying to do is, create a data class where top level params are the common items that will be accessed, and in the constructors, have additional params that fit certain situations. The purpose is so that I can do something like
// something along the lines of
if (!haveSize()){
val person = User("foo", 5, "M")
} else {
val person = User("foo", 5)
}
}
Any ideas?
In Kotlin you do not need separate constructors for defining optional constructor params. You can define them all in a single constructor with default values or make them nullable, like this:
data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
val x = User("foo", 5, "L")
val y = User("foo", 5)
println(x.size) // "L" from call site
println(y.size) // "M" from default param
}
You can not access size variable, because this is from secondary construct, but we have alternative variant.
data class User(var name: String, var age: Int) {
var size: String
init {
size = "size"
}
constructor(name: String, age: Int, size: String) : this(name, age) {
this.size = size
}
}
In short, you want to have one property that can be one of a limited number of options. This could be solved using generics, or sealed inheritance.
Generics
Here I've added an interface, MountDetails, with a generic parameter, T. There's a single property, val c, which is of type T.
data class User(
val mountOptions: MountOptions,
val mountDetails: MountDetails<*>,
)
data class MountOptions(
val a: String,
val b: String
)
interface MountDetails<T : Any> {
val c: T
}
data class MountOneDetails(override val c: Int) : MountDetails<Int>
data class MountTwoDetails(override val c: String) : MountDetails<String>
Because the implementations MountDetails (MountOneDetails and MountTwoDetails) specify the type of T to be Int or String, val c can always be accessed.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
println(user.mountDetails)
}
fun main() {
val mt = MountOptions("foo", "bar")
val mountOneDetails = MountOneDetails(111)
anotherCaller(User(mt, mountOneDetails))
val mountTwoDetails = MountTwoDetails("mount two")
anotherCaller(User(mt, mountTwoDetails))
}
Output:
foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)
Generics have downsides though. If there are lots of generic parameters it's messy, and it can be difficult at runtime to determine the type of classes thanks to type-erasure.
Sealed inheritance
Since you only have a limited number of mount details, a much neater solution is sealed classes and interfaces.
data class User(val mountOptions: MountOptions)
sealed interface MountOptions {
val a: String
val b: String
}
data class MountOneOptions(
override val a: String,
override val b: String,
val integerData: Int,
) : MountOptions
data class MountTwoOptions(
override val a: String,
override val b: String,
val stringData: String,
) : MountOptions
The benefit here is that there's fewer classes, and the typings are more specific. It's also easy to add or remove an additional mount details, and any exhaustive when statements will cause a compiler error.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
// use an exhaustive when to determine the actual type
when (user.mountOptions) {
is MountOneOptions -> println(user.mountOptions.integerData)
is MountTwoOptions -> println(user.mountOptions.stringData)
// no need for an 'else' branch
}
}
fun main() {
val mountOne = MountOneOptions("foo", "bar", 111)
anotherCaller(User(mountOne))
val mountTwo = MountTwoOptions("foo", "bar", "mount two")
anotherCaller(User(mountTwo))
}
Output:
foo
bar
111
foo
bar
mount two
This is really the "default values" answer provided by Hubert Grzeskowiak adjusted to your example:
data class OneDetails(val c: Int)
data class TwoDetails(val c: String)
data class MountOptions(val a: String, val b: String)
data class User(
val mountOptions: MountOptions,
val detailsOne: OneDetails? = null,
val detailsTwo: TwoDetails? = null
)
fun main() {
fun anotherCaller(user: User) = println(user)
val mt = MountOptions("foo", "bar")
val one = OneDetails(1)
val two = TwoDetails("2")
val switch = "0"
when (switch) {
"0" -> anotherCaller(User(mt))
"1" -> anotherCaller(User(mt, detailsOne = one))
"2" -> anotherCaller(User(mt, detailsTwo = two))
"12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
else -> throw IllegalArgumentException(switch)
}
}

How to deserialize an array of values into a collection using kotlinx serialization

Hi I am a newbie to kotlinx serialization and I am using KMP, my requirement is a little different
my data class
#Serializable data class Student(val name : String , val age : Int)
and my simple JSON would be "['Avinash', 22]",
which should be deserialized to Student("Avinash", 22)
I'm not able to deserialize it can anyone help me
While input such as [Avinash, 22] is not well formed Json, you can still
work with it by parsing it into a JsonElement:
import kotlinx.serialization.json.*
data class Student(val name: String, val age: Int)
fun decode(stringData: String, parser: Json): List<Student> {
val element: JsonArray = parser.parseToJsonElement(stringData).jsonArray
return element.windowed(2, 2).map {
Student(
it[0].toString(),
it[1].toString().toInt()
)
}
}
fun main() {
val parser = Json { isLenient = true }
val students = decode("[A, 22, B, 33, C, 44]", parser)
println(students)
// [Student(name=A, age=22), Student(name=B, age=33), Student(name=C, age=44)]
}
Try this:
val student: Student = Json.decodeFromString("{\"name\": \"Avinash\", \"age\": \"22\"}")
Pay attention how to format your JSON string.
[] square brackets are for arrays
{} curly brackets are for objects
And you must provide your fields name, and use double quotes for fields and values, or use a less strict Json deserialization:
val json = Json {
isLenient = true
}
val student: Student = json.decodeFromString("{name: Avinash, age: 22}")
If you want a deep view on json schema, you can read here.

Sort list in kotlin by variable property given as parameter

I know it's possible to sort a list of person by a property using following syntax:
persons = persons.sortedBy { person: Person-> person.name }
Now i want to write a sort function which accepts the property by which i want the list to be sorted by as an parameter.
In JavaScript i would write something like this:
fun sort(property: String) {
persons = persons.sortedBy { person: Person-> person[property]}
}
But i cannot write "person[property]" in Kotlin.
So how can i achieve such a sorting function in Kotlin?
Now i want to write a sort function which accepts the property by which i want the list to be sorted by as an parameter.
Let's hope that by "the property", you can settle for something like a function reference:
data class Person(val name: String, val age: Int)
var people = listOf(Person("Jane Doe", 25), Person("John Smith", 24), Person("Jen Jones", 37))
fun <R: Comparable<R>> sort(selector: (Person) -> R) {
people = people.sortedBy(selector)
}
fun main() {
println(people)
sort(Person::name)
println(people)
}
I could also use sort(Person::age) if I wanted to sort by age.
If your pseudocode is more literal, and you want a String parameter for the property name, you should start by asking yourself "why?". But, if you feel that you have a legitimate reason to use a String, one approach would be:
data class Person(val name: String, val age: Int)
var people = listOf(Person("Jane Doe", 25), Person("John Smith", 24), Person("Jen Jones", 37))
fun sort(property: String) {
people = when(property) {
"name" -> people.sortedBy(Person::name)
"age" -> people.sortedBy(Person::age)
else -> throw IllegalArgumentException("and this is why you should not be doing this")
}
}
fun main() {
println(people)
sort("name")
println(people)
}
class CustomObject(val customProperty: String, val value:Int)
val list = ArrayList<CustomObject>()
list.add(CustomObject("Z",5))
list.add(CustomObject("A",4))
list.add(CustomObject("B",1))
list.add(CustomObject("X",6))
list.add(CustomObject("Aa",7))
var alphabeticallySortedList = list.sortedWith(compareBy { it.customProperty })
var numberWiseSortedList = list.sortedWith(compareBy { it.value })

Can I set an object variable with a reflection value to a property of another object?

I have a class that has a property whose type is KMutableVar1. The objects of that class have a variable assigned to a reflection of another class's property. I have a function that is supposed take in an object of the first class and uses its variable of type KMutableVar1 to determine which property of an object of the second class to edit.
jeez that paragraph is awful im so so sorry ><
I have already tried assigning the object's KMutableVar1 variable to another variable and then trying to tie that variable to an object using dot notation, but that variable name isn't in the primary constructor for the class and thus an error occurs.
class Thing(var amount: Int, var id: Int){
fun editAttributes(object: Thing, editor: RemoteEdit){
//My initial thought here was to do the following:
var editing = editor.attributeToEdit
object.editing = editor.newValue
//But this raises an error since class 'thing' has no attribute 'editing'
}
}
var bananas = Thing(amount = 12, id = 21)
class RemoteEdit(var attributeToEdit: KMutableVar1, var newValue: Int)
var remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
My intent is for the function to change bananas.amount to 23.
Sorry, I'm not fully understanding why you need it but it will work I guess:
import kotlin.reflect.KMutableProperty1
class Thing(var amount: Int, var id: Int) {
fun editAttributes(editor: RemoteEdit) {
val editing = editor.attributeToEdit
editing.set(this, editor.newValue)
}
}
class RemoteEdit(var attributeToEdit: KMutableProperty1<Thing, Int>, var newValue: Int)
fun main() {
val bananas = Thing(amount = 12, id = 21)
val remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
bananas.editAttributes(remoteEditor)
println(bananas.amount) // prints 23
}
I have a feeling this might be an XY problem because there are so many unusual things going on in your code. Why would the implementation of changing the property value through reflection be in the class that's being edited?
I suppose if there is some reason you need to be able to pass these parameters for editing around, you would need a class, but then it makes sense for it to implement the function for using it by itself:
class RemoteEdit<T, R>(var attributeToEdit: KMutableProperty1<T, R>, var newValue: R) {
fun execute(item: T) {
attributeToEdit.set(item, newValue)
}
}
val bananas = Thing(amount = 12, id = 21)
val edit23 = RemoteEdit(Thing::amount, 23)
edit23.execute(bananas)
If you don't need to pass these around, all you need is a top level function:
fun <T, R> editProperty(item: T, attributeToEdit: KMutableProperty1<T, R>, newValue: R) =
attributeToEdit.set(item, newValue)
val bananas = Thing(amount = 12, id = 21)
editProperty(bananas, Thing::amount, 23)

Using Moshi with multiple input fields

I have some JSON that looks like this:
{
"name" : "Credit Card",
"code" : "AUD",
"value" : 1000
}
and am using Moshi to unmarshall this into a data structure like:
data class Account(
#Json(name = "name")
val name: String,
#Json(name = "currency")
val currency: String,
#Json(name = "value")
val value: Int
)
Everything works well. However, I really would like to extract the currency and value parameters into a separate Money object. So my model looks more like:
data class Money(
#Json(name = "currency")
val currency: String,
#Json(name = "value")
val value: Int
)
data class Account(
#Json(name = "name")
val name: String,
#Json(name = "???")
val money: Money
)
The challenge I'm struggling with is how to annotate things so that the Money object can be given two different fields (currency and value) that come from the same level as the parent account.
Do I need to create an intermediate "unmarshalling" object called, say, MoshiAccount and then use a custom adapter to convert that to my real Account object?
I saw How to deseralize an int array into a custom class with Moshi? which looks close (except that in that case, the adapted object (VideoSize) only needs a single field as input... in my case, I need both currency and value)
Any thoughts or suggestions would be much appreciated. Thanks
Moshi's adapters can morph your JSON structure for you.
object ADAPTER {
private class FlatAccount(
val name: String,
val currency: String,
val value: Int
)
#FromJson private fun fromJson(json: FlatAccount): Account {
return Account(json.name, Money(json.currency, json.value))
}
#ToJson private fun toJson(account: Account): FlatAccount {
return FlatAccount(account.name, account.money.currency, account.money.value)
}
}
Don't forget to add the adapter to your Moshi instance.
val moshi = Moshi.Builder().add(Account.ADAPTER).add(KotlinJsonAdapterFactory()).build()
val adapter = moshi.adapter(Account::class.java)