Skip to content

Instantly share code, notes, and snippets.

@L-Briand
Last active February 13, 2025 08:24
Show Gist options
  • Save L-Briand/a8cf83c108b9135de98d9380ec97bb5e to your computer and use it in GitHub Desktop.
Save L-Briand/a8cf83c108b9135de98d9380ec97bb5e to your computer and use it in GitHub Desktop.
A Gson TypeAdapter for Kotlin’s ByteArray to and from Base64 String.
import android.util.Base64
import com.google.gson.Gson
import com.google.gson.JsonParseException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
/**
* A custom Gson `TypeAdapter` implementation for serializing and deserializing `ByteArray`
* objects to and from Base64-encoded strings.
*
* This adapter converts a `ByteArray` to a Base64-encoded string during serialization and
* decodes a Base64 string back into a `ByteArray` during deserialization. The adapter also
* handles cases like null values and empty strings.
*
* The companion `Factory` object can be used to register this adapter with Gson for types
* of `ByteArray`.
*
* Serialization:
* - Null `ByteArray` values are written as `null`.
* - Empty `ByteArray` values are written as an empty string `""`.
* - Non-empty `ByteArray` values are encoded into a Base64 string.
*
* Deserialization:
* - Null JSON values are deserialized into `null`.
* - Empty strings aŒre deserialized into an empty `ByteArray`.
* - Valid Base64 strings are decoded into their corresponding `ByteArray`.
* - If an invalid type is encountered, a `JsonParseException` is thrown.
*/
class GsonByteArrayB64TypeAdapter : TypeAdapter<ByteArray>() {
override fun write(output: JsonWriter?, value: ByteArray?) {
output ?: return
if (value == null) {
// even if output.serializeNull is false, we need to call nullValue() for gson to go onto the next element
output.nullValue()
return
}
if (value.isEmpty()) {
output.value("")
return
}
val oldHtmlSafeValue = output.isHtmlSafe
try {
output.isHtmlSafe = false
output.value(Base64.encodeToString(value, Base64.NO_WRAP))
} finally {
output.isHtmlSafe = oldHtmlSafeValue
}
}
override fun read(input: JsonReader?): ByteArray? {
input ?: return null
when (input.peek()) {
JsonToken.NULL -> {
input.nextNull()
return null
}
JsonToken.STRING -> {
val stringValue = input.nextString()
if (stringValue.isEmpty()) return byteArrayOf()
else return Base64.decode(stringValue, Base64.NO_WRAP)
}
else -> throw JsonParseException("Expected a Base64 String but was ${input.peek()}. At ${input.path}")
}
}
object Factory : TypeAdapterFactory {
override fun <T : Any?> create(
gson: Gson?,
type: TypeToken<T?>?
): TypeAdapter<T?>? {
if (type == null) return null
if (type.rawType != ByteArray::class.java) return null
@Suppress("UNCHECKED_CAST")
return GsonByteArrayB64TypeAdapter() as TypeAdapter<T?>
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment