1+ /* -
2+ * #%L
3+ * kotlin-insight-client-api
4+ * %%
5+ * Copyright (C) 2022 - 2023 linked-planet GmbH
6+ * %%
7+ * Licensed under the Apache License, Version 2.0 (the "License");
8+ * you may not use this file except in compliance with the License.
9+ * You may obtain a copy of the License at
10+ *
11+ * http://www.apache.org/licenses/LICENSE-2.0
12+ *
13+ * Unless required by applicable law or agreed to in writing, software
14+ * distributed under the License is distributed on an "AS IS" BASIS,
15+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+ * See the License for the specific language governing permissions and
17+ * limitations under the License.
18+ * #L%
19+ */
20+ package com.linkedplanet.kotlininsightclient.api.impl
21+
22+ import com.google.gson.Gson
23+ import com.google.gson.JsonElement
24+ import com.google.gson.JsonObject
25+ import com.google.gson.JsonParseException
26+ import com.google.gson.JsonPrimitive
27+ import com.google.gson.TypeAdapter
28+ import com.google.gson.TypeAdapterFactory
29+ import com.google.gson.reflect.TypeToken
30+ import com.google.gson.stream.JsonReader
31+ import com.google.gson.stream.JsonWriter
32+ import kotlin.reflect.KClass
33+
34+ /* *
35+ * Creates TypeAdaptors for sealed classes.
36+ * Especially for deserialization Gson needs to know which class to instantiate.
37+ *
38+ * @param baseType The parent sealed class
39+ * @param typeFieldName the additional field inside the json that contains the name of the type
40+ * @param jsonNameForType Choose a different name for the type einside the json, other than the name of the class itself (defaults to simplename)
41+ */
42+ class SealedTypeAdapterFactory <T : Any > private constructor(
43+ private val baseType : KClass <T >,
44+ private val typeFieldName : String ,
45+ jsonNameForType : (KClass <out T >) -> Pair <String , KClass <out T >>
46+ ) : TypeAdapterFactory {
47+
48+ private val subclasses = baseType.sealedSubclasses
49+ private val nameToSubclass: Map <String , KClass <out T >> = subclasses.associate(jsonNameForType)
50+
51+ init {
52+ if (! baseType.isSealed) throw IllegalArgumentException (" $baseType is not a sealed class" )
53+ }
54+
55+ override fun <R : Any > create (gson : Gson , type : TypeToken <R >? ): TypeAdapter <R >? {
56+ if (type == null || subclasses.isEmpty() || subclasses.none { type.rawType.isAssignableFrom(it.java) }) return null
57+ val elementTypeAdapter = gson.getAdapter(JsonElement ::class .java)
58+ val subclassToDelegate: Map <KClass <* >, TypeAdapter <* >> = subclasses.associateWith {
59+ gson.getDelegateAdapter(this , TypeToken .get(it.java))
60+ }
61+
62+ return object : TypeAdapter <R >() {
63+
64+ override fun write (writer : JsonWriter , value : R ) {
65+ val srcType = value::class
66+ val label = srcType.simpleName!!
67+
68+ @Suppress(" UNCHECKED_CAST" ) val delegate = subclassToDelegate[srcType] as TypeAdapter <R >
69+ val jsonObject = delegate.toJsonTree(value).asJsonObject
70+
71+ val clone = JsonObject ()
72+ if (! jsonObject.has(typeFieldName)) {
73+ clone.add(typeFieldName, JsonPrimitive (label))
74+ }
75+ jsonObject.entrySet().forEach {
76+ clone.add(it.key, it.value)
77+ }
78+ elementTypeAdapter.write(writer, clone)
79+ }
80+
81+ override fun read (reader : JsonReader ): R {
82+ val element = elementTypeAdapter.read(reader)
83+ val labelElement = element.asJsonObject.remove(typeFieldName) ? : throw JsonParseException (
84+ " cannot deserialize $baseType because it does not define a field named $typeFieldName "
85+ )
86+ val name = labelElement.asString
87+ val subclass = nameToSubclass[name] ? : throw JsonParseException (" cannot find $name subclass of $baseType " )
88+ @Suppress(" UNCHECKED_CAST" )
89+ return (subclass.objectInstance as ? R ) ? : (subclassToDelegate[subclass]!! .fromJsonTree(element) as R )
90+ }
91+
92+ }
93+ }
94+
95+ companion object {
96+ fun <T : Any > of (
97+ clz : KClass <T >,
98+ typeFieldName : String = "type",
99+ jsonNameForType : (KClass <out T >) -> Pair <String , KClass <out T >> = { it.simpleName!! to it }
100+ ) = SealedTypeAdapterFactory (clz, typeFieldName, jsonNameForType)
101+ }
102+ }
0 commit comments