How to serialize a reasonably complex type that is...
# serialization
r
How to serialize a reasonably complex type that is not in my control? I am interested in storing a Google Places API response, but there are a plethora of types that of course aren't marked as `@Serializable`; to avoid adding custom logic for each, is there an easy way to create the serializers as if I the classes had the annotation? Or is the best options to use a reflection based serialzer like gson?
b
Map<String, Any?>
r
The type in question has
results: Array<PlacesSearchResult>
as a param; out of the box, kotlin serialization won't know how to turn that into json
and then that type has similar subtypes, etc
b
It will, the items will be represented as maps under the hood
Or you can just use JsonObject type
It has some safer accessors and checks when navigating json trees
r
So the Map may work for deserialization, but what about serialization? Also, is there a way to do this such that I keep around the original type? Trying to encodeToString this type expectedly gives:
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'PlacesSearchResponse' is not found.
Conceptually, the
@Serializable
annotation says "at compile time, reflect over this object and create all the serializer logic, so at runtime we don't need to do this reflection", which seems to me that I should be able to apply this to types from other modules (i.e., why can't the plugin do the same thing for typed defined outside my module, ideally recursively)
At least, the above is my understanding
b
If you mix in custom types in the tree you need to either annotate them with @Serializable or write your own custom serializer
r
But that's the issue of course - this is a type from a separate module, which uses other types in a separate module, etc. Gson can serialize this I believe because it can use reflection to get the types and types of subtypes. In my mind kotlin's serialization plugin should be able to, but I have never read how. I don't want to write a custom serializer, because there are a lot of types, subtypes, etc, that I don't want to have to investigate. This seems like a reasonably common situation, so I would think something exists to do this reasonably easily without having to go through everything field by field in a custom serializer
b
Why can't you just deserialize into JsonObject and then write a converter into your custom type? Or the other way around for serialization.
Reflection is generally avoided as much as possible in kotlinx world
r
For example, under the top level type, is an array of Results, which has things like 'Geometry', which has type Bounds, which has type LatLng, all of which would have to be handled with custom code
I can deserialize to JsonObject; I can't serialize from the original type. Also reverting the JsonObject or Map of course loses all of the pleasantness of having a dedicated type
Yes reflection is avoided; but every tool has its place
b
If the object schema is static, you could just take sample json of that and generate @serializable classes for it via an IDE plugin (json to kt I think)
r
I could, but I'd prefer to use the already existing type - which I could do by relying on gson for this serialization, but I'd rather both keep the type and use kotlin's built in serialization mechanism without oodles of glue code
If you don't know a way to do this, that's fine 😛 If you know authoritatively it's impossible in kotlinx, that's useful, but I'm not really looking for workarounds
b
I see. Well I think you need custom serializer in the end
e
there's
@Serializer(forClass = )
r
ephemient, do you have a short example of usage?
b
It's to be placed on your custom serializer class to link it to type.
That way you don't have to explicitly register it in each serializersModule
r
Oh right, yeah that is what you were suggesting originally
I think just relying on Moshi or Gson is the way to go for this; potentially could wrap that usage inside a custom serializer, and leave all the heavy lifting to reflection based libraries
e
no, it doesn't auto-register;
@file:UseSerializers
sorta does that, in a file-local way
but
@Serializer
will let you use the auto-generated serializer for external classes
unfortunately if they're Java classes I don't think it'll work
you could probably use a similar approach to generate your own custom serializers via kapt though
h
You could use https://github.com/EsotericSoftware/kryo which works without types being tagged with
Serializable
r
FWIW, I got this to work using Moshi via:
Copy code
// This could be shorter - I think this uses an older interface which was simplified
class UrlAdapter : JsonAdapter<URL>(){
    @ToJson
    override fun toJson(writer: JsonWriter, value: URL?) {
        value?.let {
            writer.value(it.toString())
        }
    }

    @FromJson
    override fun fromJson(reader: JsonReader): URL? {
        return if (reader.peek() != JsonReader.Token.NULL) {
            return URL(reader.nextString())
        } else {
            reader.nextNull<Any>()
            null
        }
    }
}

class LocalTimeAdapter {
    @ToJson
    fun toJson(value: LocalTime): String {
        return value.toString()
    }

    @FromJson
    fun fromJson(value: String): LocalTime {
        return LocalTime.parse(value)
    }
}

val moshi = Moshi
            .Builder()
            .add(UrlAdapter())
            .add(LocalTimeAdapter())
            .addLast(KotlinJsonAdapterFactory())
            .build()
val adapter = moshi.adapter(PlacesSearchResponse::class.java)
val json = adapter.toJson(resp)
Not overhead free, but I think getting this setup in kotlinx serialization would require a lot more preparation for custom type serializers. If this is doable in kotlinx serialization with a similar number of steps, I'd love to see it 🙂
e
following up on my previous comment,
you could probably use a similar approach to generate your own custom serializers via kapt though
done, with a test to prove that it works. not too hard, just needed a few special cases for the few model classes that don't follow the same structure as all the others (edited)
577 Views