Gson annotations with Kotlin - kotlin

I have a SpringBoot project with Kotlin, and I am using Gson to read and write Json. I am trying to annotate some fields away from Json by custom annotations:
data class Order(
val external: Boolean,
val orderNumber: String,
val companyName: String,
#Exclude val vatCode: String,
)
Here is how I define strategy:
private final var strategy: ExclusionStrategy = object : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>?): Boolean {
return false
}
override fun shouldSkipField(field: FieldAttributes): Boolean {
return field.getAnnotation(Exclude::class.java) != null
}
}
And implement it here:
val gson = GsonBuilder().setExclusionStrategies(strategy).create()
But it won't work. It seems that annotation is not recognized / cannot be read by strategy functions. What might cause the problem?

Related

How to use the data type java.util.UUID in Moshi?

I used the data type java.util.UUID in my data models and I have used Moshi for serialization.
But I encountered an error saying that "Platform class java.util.UUID requires explicit JsonAdapter to be registered"
I have gone through the documentation of Moshi for writing custom adapters and I tried to replicate it accordingly.
I wrote an adapter and added it to a moshi instance. But still I encounter the same error .
Adapter
class UUIDAdapter {
#ToJson
fun toJson(value:java.util.UUID):java.util.UUID{
return value
}
#FromJson
fun fromJson(value: String):java.util.UUID{
return java.util.UUID.fromString(value)
}
}
Model
#JsonClass(generateAdapter = true)
data class AddWorkspace(
#Json(name = "user_id")
val user_id: UUID,
#Json(name = "name")
val name:String,
#Json(name = "descp")
val descp:String,
#Json(name = "created_at")
val created_at:String
)
Moshi
private val moshi = Moshi.Builder()
.add(UUIDAdapter())
.build()
private val retrofitBuilder = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
What else am I missing so that I can use the adapter correctly ?
Edit : Well, the methods toJson and fromJson are not being called in the first place. I tried to implement the JsonAdapter class and override the methods toJson and fromJson, but the issue I face here is that in case of the method toJson, I need to send a java.util.UUID value, but the JsonWriter cannot write a value of such data type.
Please suggest me a way to work my way through this. Thanks :)
UUID adapter
class UUIDAdapter:JsonAdapter<UUID>(){
#FromJson
override fun fromJson(reader: JsonReader): UUID? {
return UUID.fromString(reader.readJsonValue().toString())
}
#ToJson
override fun toJson(writer: JsonWriter, value: UUID?) {
writer.jsonValue(value)
}
}
You're so close. Change the #ToJson to this:
#ToJson
fun toJson(value:java.util.UUID): String {
return value.toString()
}
as Jesse described just use:
class UuidAdapter {
#FromJson
fun fromJson(uuid: String): UUID = UUID.fromString(uuid)
#ToJson
fun toJson(value: UUID): String = value.toString()
}

Implementing observable properties that can also serialize in Kotlin

I'm trying to build a class where certain values are Observable but also Serializable.
This obviously works and the serialization works, but it's very boilerplate-heavy having to add a setter for every single field and manually having to call change(...) inside each setter:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
#Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
correctly outputs
changing value2
{"value2":"test2"}
I've tried introducing Delegates:
interface Observable {
fun change(message: String) {
println("changing $message")
}
#Suppress("ClassName")
class default<T>(defaultValue: T) {
private var value: T = defaultValue
operator fun getValue(observable: Observable, property: KProperty<*>): T {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
#Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" }) correctly triggers change detection, but it doesn't serialize:
changing value1
{}
If I go from Observable to ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
#Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
the result is the same:
changing blah!
{}
Similarly for Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
outputs:
changing value4
{}
Delegates just doesn't seem to work with Kotlin Serialization
What other options are there to observe a property's changes without breaking its serialization that will also work on other platforms (KotlinJS, KotlinJVM, Android, ...)?
Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now.
There is an open issue #1578 on GitHub regarding this feature.
According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original object. Also you could write a custom serializer to support the serialization of Kotlin Delegates, which seems to be even more boilerplate, then writing custom getters and setters, as proposed in the question.
Data Transfer Object
By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.
This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as #Serializable.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
#Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
This yields the following result:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
Custom data type
If changing the data type is an option, you could write a wrapping class which gets (de)serialized transparently. Something along the lines of the following might work.
#Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
Which yields the following result:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
You however lose information about which property changed, as you don't have easy access to the field name. Also you have to change your data structures, as mentioned above and might not be desirable or even possible. In addition, this work only for Strings for now, even though one might make it more generic though.
Also, this requires a lot of boilerplate to start with. On the call site however, you just have to wrap the actual value in an call to obs.
I used the following boilerplate to get it to work.
typealias OnChange = (before: String, after: String) -> Unit
#Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}

kotlin idiomatic way to make it simpler when pass in a nullable mutableMap

converting from java to kotlin
java code
public void logEvent(String eventName, #Nullable Map<String, String> customParams) {
if (customParams == null) {
customParams = new HashMap<>();
}
customParams.put(OTHER_REQUIRED_KEY, OTHER_REQUIRED_VALUE);
service.doLogEvent(eventName, customParams);
}
kotlin code
fun logEvent(eventName: String, customParams: Map<String, String>?) {
var customParamsMap = HashMap<String, String>()
if (customParams != null) {
customParamsMap.putAll(customParams)
}
customParamsMap[OTHER_REQUIRED_KEY] = OTHER_REQUIRED_VALUE
service.doLogEvent(eventName, customParamsMap)
}
the kotlin code will create the temp map regardless if the passed in map is null or not.
is there a better way to avoid this map creation?
This is as simple as:
fun logEvent(eventName: String, customParams: MutableMap<String, String>?) {
val customParamsMap = customParams ?: mutableMapOf()
...
}
Or you can specify a default value for customParams:
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
...
}
Note that in both examples I changed the type of customParams to MutableMap. This is a direct equivalent of the Java code. If it requires to be a read-only Map then you actually need to copy elements to a new map:
fun logEvent(eventName: String, customParams: Map<String, String>?) {
val customParamsMap = customParams?.toMutableMap() ?: mutableMapOf()
...
}
The other answer is great for a one-to-one translation of the Java code. But if you are able to change the signature, you can make it more user friendly in Kotlin by making the parameter optional rather than nullable.
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
// no need for "customParamsMap`. Use "customParams" directly.
// ...
}
But either way, in my opinion it is not user friendly to require the passed map to be mutable. And presumably there aren't so many possible parameters that we are worried about the performance of copying them over. I would write the function like this, simple and flexible:
fun logEvent(eventName: String, customParams: Map<String, String> = emptyMap()) {
service.doLogEvent(eventName, customParams + (OTHER_REQUIRED_KEY to OTHER_REQUIRED_VALUE))
}

Map Key Values to Dataclass in Kotlin

how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...

Transform data class to map kotlin

My problem is that I need to transform a data class in kotlin to a map, because I need to work with this structure as a requirement, because this response will be used for a groovy classes and there is a post-process where there are validations iterations etc, with this map. My data class is the next (Podcast):
data class PodCast(val id: String, val type: String, val items: List<Item>, val header: Header, val cellType:String? = "")
data class Item(val type: String, val parentId: String, val parentType: String, val id: String, val action: Action, val isNew: Boolean)
data class Header(val color: String, val label: String)
data class Action(val type: String, val url: String)
I made the transformation manually, but I need a more sophisticated way to achieve this task.
Thanks.
You can also do this with Gson, by serializing the data class to json, and then deserializing the json to a map. Conversion in both directions shown here:
val gson = Gson()
//convert a data class to a map
fun <T> T.serializeToMap(): Map<String, Any> {
return convert()
}
//convert a map to a data class
inline fun <reified T> Map<String, Any>.toDataClass(): T {
return convert()
}
//convert an object of type I to type O
inline fun <I, reified O> I.convert(): O {
val json = gson.toJson(this)
return gson.fromJson(json, object : TypeToken<O>() {}.type)
}
See similar question here
I have done this very simple. I got the properties of the object, just using the .properties groovy method, which gave me the object as a map.