Skip to content

Instantly share code, notes, and snippets.

@Miha-x64
Last active February 9, 2022 10:01
Show Gist options
  • Save Miha-x64/48bc549caf1a98f6dd6f4138fbb883cb to your computer and use it in GitHub Desktop.
Save Miha-x64/48bc549caf1a98f6dd6f4138fbb883cb to your computer and use it in GitHub Desktop.
TypeAdapter(Factory) for enums with no reflective @SerializedName lookup; TypeAdapterFactory whichs treats Set<E> as EnumSet<E>
package net.aquadc.common
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import java.lang.reflect.ParameterizedType
import java.util.*
/**
* This [TypeAdapterFactory] makes every [Set<E>] to be handled like [EnumSet<E>], if E is Enum<E>,
* so you can just write [Set<SomeEnum>] in your classes,
* and enums will be deserialized into fast & lightweight [EnumSet]s.
*/
object EnumSetTypeAdapterFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType != Set::class.java)
return null
val elementType = (type.type as ParameterizedType).actualTypeArguments[0]
if (elementType !is Class<*> || !elementType.isEnum)
return null
@Suppress("UNCHECKED_CAST") // it's safe, I PROVE IT
return gson.getAdapter(TypeToken.getParameterized(EnumSet::class.java, elementType)) as TypeAdapter<T>
}
}
package net.aquadc.common
import com.google.gson.Gson
import com.google.gson.GsonBuilder
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 java.io.IOException
import java.util.HashMap
/**
* [TypeAdapterFactory] for [EnumTypeAdapter]
* Intended as a drop-in solution if you use ProGuard which strips out enum constants' fields,
* but you don't use @[SerializedName] on them so these fields are redundant.
*
* <a href="https://gist.github.com/Miha-x64/48bc549caf1a98f6dd6f4138fbb883cb">EnumTypeAdapterFactory on Gist</a>
*/
object EnumTypeAdapterFactory : TypeAdapterFactory {
override fun <T> create(gson: Gson, typeToken: TypeToken<T>): TypeAdapter<T>? {
var rawType = typeToken.rawType
if (!Enum::class.java.isAssignableFrom(rawType) || rawType == Enum::class.java) {
return null
}
if (!rawType.isEnum) {
rawType = rawType.superclass // handle anonymous subclasses
}
@Suppress("UPPER_BOUND_VIOLATED", "UNCHECKED_CAST")
return EnumTypeAdapter<T>(rawType as Class<T>)
}
}
typealias FallbackValueSupplier<E> = (enumType: Class<E>, serializedName: String) -> E
// usage: .registerEnumTypeAdapter<Status>({ serializedName }, { _, _ -> Status.Unknown })
inline fun <reified E : Enum<E>> GsonBuilder.registerEnumTypeAdapter(
noinline getSerializedName: E.() -> String = EnumTypeAdapter.getEnumName,
noinline getFallbackValue: FallbackValueSupplier<E> = EnumTypeAdapter.throwNoSuchElement(getSerializedName)
): GsonBuilder =
registerTypeAdapter(E::class.java, EnumTypeAdapter(E::class.java, getSerializedName, getFallbackValue))
/**
* This [TypeAdapterFactory] works with enums, but does no reflective @[SerializedName] lookup.
*/
class EnumTypeAdapter<T : Enum<T>>(
private val classOfT: Class<T>,
getSerializedName: T.() -> String = getEnumName,
private val getFallbackValue: FallbackValueSupplier<T> = throwNoSuchElement(getSerializedName)
) : TypeAdapter<T>() {
private val nameToConstant = HashMap<String, T>() // or SimpleArrayMap :)
private val constantToName = HashMap<T, String>()
init {
for (constant in classOfT.enumConstants) {
val name = constant.getSerializedName()
nameToConstant.put(name, constant)
constantToName.put(constant, name)
}
}
@Throws(IOException::class)
override fun read(`in`: JsonReader): T? {
if (`in`.peek() == JsonToken.NULL) {
`in`.nextNull()
return null
}
val name = `in`.nextString()
return nameToConstant[name] ?: getFallbackValue(classOfT, name)
}
@Throws(IOException::class)
override fun write(out: JsonWriter, value: T?) {
out.value(if (value == null) null else constantToName[value])
}
companion object {
val getEnumName = { e: Enum<*> -> e.name }
fun <E : Enum<E>> throwNoSuchElement(getSerializedName: E.() -> String) =
{ enumType: Class<E>, serializedName: String ->
val enumConstantsWithSerializedNames =
enumType.enumConstants.joinToString { "$it(${it.getSerializedName()})" }
throw NoSuchElementException(
"Enum constant $serializedName was not found in $enumType, " +
"got only $enumConstantsWithSerializedNames")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment