1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
package at.hannibal2.skyhanni.utils
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import kotlin.reflect.*
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import com.google.gson.internal.`$Gson$Types` as InternalGsonTypes
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class KSerializable
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class ExtraData
class KotlinTypeAdapterFactory : TypeAdapterFactory {
internal data class ParameterInfo(
val param: KParameter,
val adapter: TypeAdapter<Any?>,
val name: String,
val field: KProperty1<Any, Any?>
)
@OptIn(ExperimentalStdlibApi::class)
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val kotlinClass = type.rawType.kotlin as KClass<T>
if (kotlinClass.findAnnotation<KSerializable>() == null) return null
if (!kotlinClass.isData) return null
val primaryConstructor = kotlinClass.primaryConstructor ?: return null
val params = primaryConstructor.parameters.filter { it.findAnnotation<ExtraData>() == null }
val extraDataParam = primaryConstructor.parameters
.find { it.findAnnotation<ExtraData>() != null && typeOf<MutableMap<String, JsonElement>>().isSubtypeOf(it.type) }
?.let { param ->
param to kotlinClass.memberProperties.find { it.name == param.name && it.returnType.isSubtypeOf(typeOf<Map<String, JsonElement>>()) } as KProperty1<Any, Map<String, JsonElement>>
}
val parameterInfos = params.map { param ->
ParameterInfo(
param,
gson.getAdapter(
TypeToken.get(InternalGsonTypes.resolve(type.type, type.rawType, param.type.javaType))
) as TypeAdapter<Any?>,
param.findAnnotation<SerializedName>()?.value ?: param.name!!,
kotlinClass.memberProperties.find { it.name == param.name }!! as KProperty1<Any, Any?>
)
}.associateBy { it.name }
val jsonElementAdapter = gson.getAdapter(JsonElement::class.java)
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T?) {
if (value == null) {
out.nullValue()
return
}
out.beginObject()
parameterInfos.forEach { (name, paramInfo) ->
out.name(name)
paramInfo.adapter.write(out, paramInfo.field.get(value))
}
if (extraDataParam != null) {
val extraData = extraDataParam.second.get(value)
extraData.forEach { (extraName, extraValue) ->
out.name(extraName)
jsonElementAdapter.write(out, extraValue)
}
}
out.endObject()
}
override fun read(reader: JsonReader): T? {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return null
}
reader.beginObject()
val args = mutableMapOf<KParameter, Any?>()
val extraData = mutableMapOf<String, JsonElement>()
while (reader.peek() != JsonToken.END_OBJECT) {
val name = reader.nextName()
val paramData = parameterInfos[name]
if (paramData == null) {
extraData[name] = jsonElementAdapter.read(reader)
continue
}
val value = paramData.adapter.read(reader)
args[paramData.param] = value
}
reader.endObject()
if (extraDataParam != null) {
args[extraDataParam.first] = extraData
}
return primaryConstructor.callBy(args)
}
}
}
}
|