Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Gson serialization #4

Merged
merged 3 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ object Dependencies {
const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serialization}"
const val serializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}"
const val jackson = "com.fasterxml.jackson.core:jackson-databind:${Versions.jackson}"
const val gson = "com.google.code.gson:gson:${Versions.gson}"
}
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ object Versions {
const val shouldko = "0.2.2"
const val serialization = "1.5.1"
const val jackson = "2.15.2"
const val gson = "2.10.1"
}
1 change: 1 addition & 0 deletions enums-gson/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
16 changes: 16 additions & 0 deletions enums-gson/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
kotlin("jvm")
id("default-config")
id("default-java-publish")
}

dependencies {
api(rootProject)
api(project(":enums"))

implementation(kotlin("stdlib"))
implementation(Dependencies.gson)

testImplementation(Dependencies.junit)
testImplementation(Dependencies.shouldko)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package pl.brightinventions.codified.gson

import com.google.gson.Gson
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.JsonWriter
import pl.brightinventions.codified.Codified
import pl.brightinventions.codified.enums.CodifiedEnum
import pl.brightinventions.codified.enums.CodifiedEnumDecoder
import java.lang.reflect.ParameterizedType

class CodifiedEnumTypeAdapter<T, C : Any>(
enumTypeToken: TypeToken<*>,
private val codeAdapter: TypeAdapter<C>,
) : TypeAdapter<CodifiedEnum<T, C>>() where T : Enum<T>, T : Codified<C> {

@Suppress("UNCHECKED_CAST")
private val enumClass = enumTypeToken.rawType as Class<T>

class Factory : TypeAdapterFactory {
override fun <T : Any> create(gson: Gson, typeToken: TypeToken<T>?): TypeAdapter<T>? {
if (typeToken?.rawType != CodifiedEnum::class.java) {
return null
}

val parameterizedType = typeToken.type as ParameterizedType
val enumType = parameterizedType.actualTypeArguments[0]
val enumTypeToken = TypeToken.get(enumType)
val codeType = parameterizedType.actualTypeArguments[1]
val codeAdapter = gson.getAdapter(TypeToken.get(codeType))

@Suppress("UNCHECKED_CAST")
return CodifiedEnumTypeAdapter(enumTypeToken, codeAdapter) as TypeAdapter<T>
}
}

override fun write(out: JsonWriter, value: CodifiedEnum<T, C>?) {
if (value != null) {
codeAdapter.write(out, value.code())
} else {
out.nullValue()
}
}

override fun read(`in`: JsonReader): CodifiedEnum<T, C> {
val code = codeAdapter.read(`in`)
return CodifiedEnumDecoder.decode(code, enumClass)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package pl.brightinventions.codified.gson

import com.google.gson.GsonBuilder
import org.junit.jupiter.api.Test
import pl.brightinventions.codified.enums.CodifiedEnum
import pl.brightinventions.codified.enums.codifiedEnum
import pl.miensol.shouldko.shouldEqual


class GsonTest {

private val gson = GsonBuilder()
.registerTypeAdapterFactory(CodifiedEnumTypeAdapter.Factory())
.create()


@Test
fun `mixed enum wrapper with known values should be serialized`() {
val wrapper = MixedEnumWrapper(StringEnum.FOO.codifiedEnum(), IntEnum.QUX.codifiedEnum())

val string = gson.toJson(wrapper)

string.shouldEqual("{\"stringEnum\":\"${StringEnum.FOO.code}\",\"intEnum\":${IntEnum.QUX.code}}")
}

@Test
fun `mixed enum wrapper with known values should be deserialized`() {
val string = "{\"stringEnum\":\"${StringEnum.FOO.code}\",\"intEnum\":${IntEnum.QUX.code}}"

val wrapper = gson.fromJson(string, MixedEnumWrapper::class.java)

wrapper.stringEnum.knownOrNull().shouldEqual(StringEnum.FOO)
wrapper.intEnum.knownOrNull().shouldEqual(IntEnum.QUX)
}

@Test
fun `mixed enum wrapper with unknown values should be serialized`() {
val wrapper = MixedEnumWrapper(CodifiedEnum.Unknown("hello"), CodifiedEnum.Unknown(123))

val string = gson.toJson(wrapper)

string.shouldEqual("{\"stringEnum\":\"hello\",\"intEnum\":123}")
}

@Test
fun `mixed enum wrapper with unknown values should be deserialized`() {
val string = "{\"stringEnum\":\"hello\",\"intEnum\":123}"

val wrapper = gson.fromJson(string, MixedEnumWrapper::class.java)

wrapper.stringEnum.code().shouldEqual("hello")
wrapper.intEnum.code().shouldEqual(123)
}

@Test
fun `known and unknown enums in a wrapper should be serialized`() {
val enums = listOf<CodifiedEnum<StringEnum, String>>(
CodifiedEnum.Known(StringEnum.BAR),
CodifiedEnum.Unknown("hello"),
CodifiedEnum.Known(StringEnum.FOO),
CodifiedEnum.Unknown("world"),
)

val wrapper = StringEnumListWrapper(enums)

val string = gson.toJson(wrapper)

string.shouldEqual("{\"stringEnums\":[\"rab\",\"hello\",\"oof\",\"world\"]}")
}

@Test
fun `known and unknown enums in a wrapper should be deserialized`() {
val string = "{\"stringEnums\":[\"rab\",\"hello\",\"oof\",\"world\"]}"

val wrapper = gson.fromJson(string, StringEnumListWrapper::class.java)

wrapper.stringEnums.size.shouldEqual(4)
wrapper.stringEnums[0].knownOrNull().shouldEqual(StringEnum.BAR)
wrapper.stringEnums[1].code().shouldEqual("hello")
wrapper.stringEnums[2].knownOrNull().shouldEqual(StringEnum.FOO)
wrapper.stringEnums[3].code().shouldEqual("world")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.brightinventions.codified.gson

import pl.brightinventions.codified.Codified

const val BAZ_CODE = 100
const val QUX_CODE = 999

enum class IntEnum(override val code: Int) : Codified<Int> {
BAZ(BAZ_CODE), QUX(QUX_CODE);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package pl.brightinventions.codified.gson

import pl.brightinventions.codified.enums.CodifiedEnum

data class MixedEnumWrapper(
val stringEnum: CodifiedEnum<StringEnum, String>,
val intEnum: CodifiedEnum<IntEnum, Int>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.brightinventions.codified.gson

import pl.brightinventions.codified.Codified

const val FOO_CODE = "oof"
const val BAR_CODE = "rab"

enum class StringEnum(override val code: String) : Codified<String> {
FOO(FOO_CODE), BAR(BAR_CODE);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.brightinventions.codified.gson

import pl.brightinventions.codified.enums.CodifiedEnum

data class StringEnumListWrapper(
val stringEnums: List<CodifiedEnum<StringEnum, String>>
)
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ rootProject.name = "codified"
include("enums")
include("enums-serializer")
include("enums-jackson")
include("enums-gson")