How to serialize an inner class as a single value with GSON - kotlin

Assume the following two clases
class Person internal constructor(var name: String, var address: Address)
class Address internal constructor(var city: String, var postcode: String)
The JSON serialization of this is
{"name":"Bob","address":{"city":"London","postcode":"LO12 345"}}
On the API output, I want to serialise the address as a single string, and not an object. e.g. {"name":"Bob","address":"London, LO12 345"}
I can achieve this if I write a custom serializer for Person, and even managed to work around the issue of only changing this single attribute instead of everything. However, I still have the issue that I now need to change how every single class that uses Address.
I am wondering if instead, there is a way to writte a serializer for Address that would only return a value instead of an object, much like a primive does. Then I am hoping that every single class that uses Address, would behave as if Address was a String straight away and serialize it as such.
Is such a thing possible?

You can write a serialiser for Address like this:
class AddressSerialiser: JsonSerializer<Address> {
override fun serialize(p0: Address, p1: Type?, p2: JsonSerializationContext?) =
JsonPrimitive("${p0.city}, ${p0.postcode}")
}
This serialiser outputs a JsonPrimitive, which is a string containing the city and postcode.
You can then register this when creating a Gson:
val gson = GsonBuilder()
.registerTypeAdapter(Address::class.java, AddressSerialiser())
.create()
println(gson.toJson(Person("John", Address("Cambridge", "CB5 8HX"))))
Output:
{"name":"John","address":"Cambridge, CB5 8HX"}

Not exactly sure if this is what you're looking for but what if you simply make it a data class? Like
data class Address internal constructor(var city: String, var postcode: String)
Then the toString() of it is a very descriptive string, and similar objects also count as equal, like
val a = Address("London", "LO12 345")
val b = Address("London", "LO12 345")
println(a) //prints: Address(city=London, postcode=LO12 345)
println(a == b) //prints: true

Related

How to count the number of properties in a complex class

I have a class with several properties. The properties, themselves, can also have their own properties. I would like to count the total number of properties. That includes both the properties in the "main" class and the properties' properties.
For instance, consider the following class
class Person {
val firstname: String = "Jurgen"
val lastname: String = "Klopp"
val address: Address = Address("Liverpool", "England")
}
where
class Address (
val city: String,
val coubtry: String
) { }
I would like the counting to add up to 5, since the Person class "contains" firstname, lastname, address, city and country. Note that the Address class also could have a another class (with its own properties) as its property. These properties should also be counted. Is it possible to count the total number of properties?
Please further note that the counting is intended to be applied to complex/multi-leveled AVRO structures (i.e. auto-generated AVRO classes).
I'm basing this off my other post on reflection, found here: How to get the relative class name of data classes
I created your two classes in a package called complexclasses.
My Main.kt looks like this:
import complexclasses.Person
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
fun main(args: Array<String>) {
val foundParams = Person::class.getAllMembers()
println(foundParams)
}
private fun KClass<*>.getAllMembers(): MutableList<String> {
val params = mutableListOf<String>()
memberProperties.forEach { member ->
params.add(member.name)
if(member.returnType.toString().substring(0, 7) == "kotlin.") {
return#forEach
}
val clazz = Class.forName(member.returnType.toString()).kotlin
params.addAll(clazz.getAllMembers())
}
return params
}
When I run the program, it outputs:
[address, city, country, firstname, lastname]
Using reflection (and a little string manipulation) I can recursively crawl non-kotlin objects and print out the names of all their members.
I didn't test this with more complicated data structures, like Lists or Maps. It's possible those would require more work, but I think this is enough to get you started, at least.
I'm not familiar with AVRO so hopefully this works with it.
Let me know if you hit any issues, I'm glad to help if this doesn't meet your requirements.

Kotlin inc() operator overloading

I have a little problem to overload inc() operator, precisely to make a postfix and a prefix one.
Here my data class
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person {
val tmp = this
this.age++;
return tmp
}
}
With this, age change before returning so it's only working for prefix version.
How can I do a postfix version of inc() operator ?
inc is expected to return a new, incremented instance of the class. Since you've got a dataclass, we can use Kotlin's convenience functions that work on dataclasses to get a new instance for you relatively effortlessly.
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person =
this.copy(age = this.age + 1)
}
Person.copy is one of several dataclass methods generated for you. It takes the same arguments as your primary constructor, with each argument defaulting to the current value on this (i.e. any arguments not passed will be the same as the corresponding values on this). So by passing only the age parameter by name, we modify only the one we want to and leave the others untouched.
There is no way to do what you’re trying to do. You are breaking the contract that the increment operator must not mutate the class. It must return a new instance of the class.

Kotlin data classes with Java super class

I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)

Kotlin data class with additional properties not in constructor

Starting out with Kotlin and wanting to make a data class
data class Person(val Email: String, val firstName: String, val lastName: String)
But let's say I want to add additional properties that I don't know at the time when I am using the constructor but I want to store this data at a later point when I am aware of it for example a person's mood (Represented as a String)
In Java I would make a data class like this. I would be able to not include it in the Constructor and make a getter where I could set it at a later time.
public class Person{
private String email;
private String firstName;
private String lastName;
private String mood;
public person (String email, String firstName, String lastName){
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
public setMood(String mood){
this.mood = mood;
}
}
Kotlin doesn't appear to have an answer on this or if it does I do not know how to phrase correctly. Hence why this question could already be answered and I am unable to find it.
I do understand that by not including mood in the data class line Kotlin may not be able to identify mood as part of the data class but aside from including it in the constructor and setting it to null I'm not sure what else to do or is that what I am supposed to do?
You should be able to just add it as a property to Person. In Kotlin, a data class is still a class, it just comes with some extras (toString, copy constructors, hashCode/equals, etc). You can still define any properties that you want.
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
In this case it is nullable, because as you stated, you might not know the mood until later.
Kotlin's data class must have first constructor, you can avoid it by not using the data keyword.
If you still want to add another property to the data class you can do the following:
data class Person(val email: String, val firstName: String, val lastName: String){
var mood: String = ""
}
This way you can do person.mood = "happy" without including it in the constructor.
Kotlin only considers the values passed to the primary constructor in terms of giving you the "for free" features that a Data class provides. Beyond that, you can add whatever additional properties you desire, but they aren't accounted for in the special code that Kotlin writes by way of you marking a class as data.
Per the Kotlin docs:
Note that the compiler only uses the properties defined inside the
primary constructor for the automatically generated functions. To
exclude a property from the generated implementations, declare it
inside the class body:
Per this, declaring properties outside of the primary constructor actually has benefits. You might be able to declare a property via the primary constructor, but choose not to.
Not only do you have to provide a primary constructor, but it has to include at least one property declaration. If you didn't do this, there would be no benefit to making the class a data class. But marking a class so does not limit what else you can do with that class.
Have you tried:
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
An alternative to #Todd's and #jingx's answers is
data class Person(val Email: String, val firstName: String, val lastName: String, var mood: String? = null)
The difference is that this way mood participates in toString/equals/hashCode/copy and that you can set mood in the constructor call. Even if that's probably not desirable for this specific case, it can be useful in others.

Filter out null in immutable objects list of fields

I have an immutable object:
class Foo(
val name: String,
val things: List<Thing>
)
A third party lib creates the Foo object with some 'null' Thing objects.
I am creating a new object:
val foo = thirdPartyGetFoo()
val filteredFoo = Foo(foo.name, foo.things.filterNotNull())
That works, however AndroidStudio greys out the filterNotNull function call and presents a warning:
Useless call on collection type: The inspection reports filter-like
calls on already filtered collections.
Is this the right way to filter that list? Should I ignore the warning or is there a better way?
You do not specify what library creates the object with nulls. Some deserialization libraries can use static factory methods which you could configure, and then have the factory method strip the null. For example, if this were Jackson you would simply:
class Foo(val name: String, val things: List<Thing>) {
companion object {
#JsonCreator
#JvmName("createFromNullable")
fun create(name: String, things: List<Thing?>) = Foo(name, things.filterNotNull())
fun create(name: String, things: List<Thing>) = Foo(name, things)
}
}
Then...
val goodFoo = jacksonObjectMapper().readValue<Foo>(someJsonWithNulls)
Maybe your library has options that are similar?
If not, and you don't have 100 of these things with this problem, I would probably create a temporary class to hold the results and convert that to the final class:
open class FooNullable(val name: String, open val things: List<Thing?>) {
open fun withoutNulls(): Foo = Foo(name, things.filterNotNull())
}
class Foo(name: String, override val things: List<Thing>) : FooNullable(name, things) {
override fun withoutNulls(): Foo = this
}
Then you can deserialize into FooNullable and just call withoutNulls() to get the other flavor that is clean. And if you accidentally call it on one without nulls already, it just does nothing.
val goodFoo = Foo("", emptyList<Thing>())
val alsoGoodFoo = goodFoo.withoutNulls() // NOOP does nothing
val badFoo = thirdPartyGetFoo()
val betterFoo = badFoo.withoutNulls() // clean up the instance
val safeFoo = thirdPartyGetFoo().withoutNulls() // all at once!
Not the cleanest, but does work. The downsides is this second step, although it looks like you were already planning on doing that anyway. But this model is safer than what you proposed since you KNOW which type of object you have and therefore you continue to be typesafe and have the compiler helping you avoid a mistake.
You don't have to use inheritance as in the above example, I was just trying to unify the API in case there was a reason to have either version in hand and know which is which, and also act upon them in a similar way.