Skip to content

Instantly share code, notes, and snippets.

@vodamiro
Last active October 18, 2018 08:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vodamiro/38886ff2212175abc6222ba3dd254a57 to your computer and use it in GitHub Desktop.
Save vodamiro/38886ff2212175abc6222ba3dd254a57 to your computer and use it in GitHub Desktop.
SafeValue and GSON type adapter
package cz.vodamiro.example
data class SafeValue<out T>(val value: T?, val failed: Boolean = false)
package cz.vodamiro.example
import com.google.gson.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/**
* Special implementation of GSON TypeAdapter, that prevents failure of deserialization when some
* property cannot be deserialized. In case of failure, `failure` property in SafeValue object type
* will be se to `true`. It can be handy when you want to prevent failure when deserialization of
* enum class is performed and given value is not a part of the enum, then it won't throw
* exception, but returns SafeValue with `failed` property set to true.
*/
class SafeValueTypeAdapter : JsonSerializer<SafeValue<*>>, JsonDeserializer<SafeValue<*>> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): SafeValue<*> {
return try {
val valueType = getTypeParameter0(typeOfT)
val result: Any = context.deserialize(json, valueType)
SafeValue(result, false)
} catch (e: Throwable) {
SafeValue(null, true)
}
}
override fun serialize(src: SafeValue<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
return if (src.failed || src.value == null) {
null
} else {
val valueType = getTypeParameter0(typeOfSrc)
try {
context.serialize(src.value, valueType)
} catch (e: Throwable) {
null
}
}
}
private fun getTypeParameter0(type: Type): Type {
return if (type !is ParameterizedType) {
Any::class.java
} else type.actualTypeArguments[0]
}
}
package cz.vodamiro.example
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import junit.framework.Assert.assertFalse
import org.junit.Before
import org.junit.Test
class SafeValueTypeAdapterTest {
private data class IntObject(val data: SafeValue<Int>)
private data class StringObject(val data: SafeValue<String>)
private data class FloatObject(val data: SafeValue<Float>)
private data class OptionalObject(val data: SafeValue<String>?)
private data class EnumObject(val data: SafeValue<Enum>)
private enum class Enum { A, B, C }
private data class SubObject(val data: SafeValue<StringObject>)
private lateinit var gson: Gson
@Before
fun setUp() {
val builder = GsonBuilder().registerTypeAdapter(SafeValue::class.java, SafeValueTypeAdapter())
gson = builder.create()
}
// region Deserialization
@Test
fun stringDeserialization() {
val value = "This is STRING"
val json = generateJson("\"$value\"")
val result: StringObject = gson.fromJson<StringObject>(json, StringObject::class.java)
assert(result.data.value == value)
assertFalse(result.data.failed)
}
@Test
fun intDeserialization() {
val value = 1
val json = generateJson("$value")
val result: IntObject = gson.fromJson<IntObject>(json, IntObject::class.java)
assert(result.data.value == value)
assertFalse(result.data.failed)
}
@Test
fun floatDeserialization() {
val value = 1f
val json = generateJson("$value")
val result: FloatObject = gson.fromJson<FloatObject>(json, FloatObject::class.java)
assert(result.data.value == value)
assertFalse(result.data.failed)
}
@Test
fun incompatibleTypeDeserialization() {
val json = generateJson("\"this is not float at all\"")
val result: FloatObject = gson.fromJson<FloatObject>(json, FloatObject::class.java)
assert(result.data.value == null)
assert(result.data.failed)
}
@Test
fun enumDeserialization() {
val value = Enum.A
val json = generateJson("\"${value.name}\"")
val result: EnumObject = gson.fromJson<EnumObject>(json, EnumObject::class.java)
assert(result.data.value == value)
assertFalse(result.data.failed)
}
@Test
fun enumFailedDeserialization() {
val json = generateJson("\"Z\"")
val result: EnumObject = gson.fromJson<EnumObject>(json, EnumObject::class.java)
assert(result.data.value == null)
assert(result.data.failed)
}
@Test
fun optionalTypeDeserialization() {
val json = generateJson("null")
val result: OptionalObject = gson.fromJson<OptionalObject>(json, OptionalObject::class.java)
assert(result.data == null)
}
@Test
fun emptyObjectDeserialization() {
val json = "{}"
val result: OptionalObject = gson.fromJson<OptionalObject>(json, OptionalObject::class.java)
assert(result.data == null)
}
@Test
fun subObjectDeserialization() {
val value = "This is STRING"
val json = generateJson(generateJson("\"$value\""))
val result: SubObject = gson.fromJson<SubObject>(json, SubObject::class.java)
assert(result.data.value?.data?.value == value)
assertFalse(result.data.failed)
}
// endregion
// region Serialization
@Test
fun stringSerialization() {
val value = "This is STRING"
val src = StringObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("\"$value\""))
}
@Test
fun intSerialization() {
val value = 100
val src = IntObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("$value"))
}
@Test
fun floatSerialization() {
val value = 1.1f
val src = FloatObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("$value"))
}
@Test
fun failedSerialization() {
val value = 1.1f
val src = FloatObject(data = SafeValue(value, failed = true))
val result: String = gson.toJson(src)
assert(result == "{}")
}
@Test
fun nullValueSerialization() {
val src = FloatObject(data = SafeValue(null, failed = false))
val result: String = gson.toJson(src)
assert(result == "{}")
}
@Test
fun optionalSerialization() {
val src = OptionalObject(data = null)
val result: String = gson.toJson(src)
assert(result == "{}")
}
@Test
fun enumSerialization() {
val value = Enum.A
val src = EnumObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("\"${value.name}\""))
}
@Test
fun subObjectSerialization() {
val str = "AAA"
val value = StringObject(data = SafeValue(str))
val src = SubObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson(generateJson("\"$str\"")))
}
@Test
fun subObjectWithNullSerialization() {
val value = StringObject(data = SafeValue(null))
val src = SubObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("{}"))
}
@Test
fun subObjectFailedSerialization() {
val value = StringObject(data = SafeValue("123", failed = true))
val src = SubObject(data = SafeValue(value))
val result: String = gson.toJson(src)
assert(result == generateJson("{}"))
}
// endregion
private fun generateJson(dataJson: String) = "{\"data\":$dataJson}"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment