diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 1dd6177..4a30fa8 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -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}" } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ee745b8..9b12091 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -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" } diff --git a/enums-gson/.gitignore b/enums-gson/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/enums-gson/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/enums-gson/build.gradle.kts b/enums-gson/build.gradle.kts new file mode 100644 index 0000000..10e02ad --- /dev/null +++ b/enums-gson/build.gradle.kts @@ -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) +} diff --git a/enums-gson/src/main/kotlin/pl/brightinventions/codified/gson/CodifiedEnumTypeAdapter.kt b/enums-gson/src/main/kotlin/pl/brightinventions/codified/gson/CodifiedEnumTypeAdapter.kt new file mode 100644 index 0000000..b74ec03 --- /dev/null +++ b/enums-gson/src/main/kotlin/pl/brightinventions/codified/gson/CodifiedEnumTypeAdapter.kt @@ -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( + enumTypeToken: TypeToken<*>, + private val codeAdapter: TypeAdapter, +) : TypeAdapter>() where T : Enum, T : Codified { + + @Suppress("UNCHECKED_CAST") + private val enumClass = enumTypeToken.rawType as Class + + class Factory : TypeAdapterFactory { + override fun create(gson: Gson, typeToken: TypeToken?): TypeAdapter? { + 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 + } + } + + override fun write(out: JsonWriter, value: CodifiedEnum?) { + if (value != null) { + codeAdapter.write(out, value.code()) + } else { + out.nullValue() + } + } + + override fun read(`in`: JsonReader): CodifiedEnum { + val code = codeAdapter.read(`in`) + return CodifiedEnumDecoder.decode(code, enumClass) + } +} \ No newline at end of file diff --git a/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/GsonTest.kt b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/GsonTest.kt new file mode 100644 index 0000000..5507cb4 --- /dev/null +++ b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/GsonTest.kt @@ -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.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") + } +} \ No newline at end of file diff --git a/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/IntEnum.kt b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/IntEnum.kt new file mode 100644 index 0000000..cca3fa8 --- /dev/null +++ b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/IntEnum.kt @@ -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 { + BAZ(BAZ_CODE), QUX(QUX_CODE); +} \ No newline at end of file diff --git a/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/MixedEnumWrapper.kt b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/MixedEnumWrapper.kt new file mode 100644 index 0000000..02afe97 --- /dev/null +++ b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/MixedEnumWrapper.kt @@ -0,0 +1,8 @@ +package pl.brightinventions.codified.gson + +import pl.brightinventions.codified.enums.CodifiedEnum + +data class MixedEnumWrapper( + val stringEnum: CodifiedEnum, + val intEnum: CodifiedEnum, +) \ No newline at end of file diff --git a/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnum.kt b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnum.kt new file mode 100644 index 0000000..2018ecd --- /dev/null +++ b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnum.kt @@ -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 { + FOO(FOO_CODE), BAR(BAR_CODE); +} \ No newline at end of file diff --git a/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnumListWrapper.kt b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnumListWrapper.kt new file mode 100644 index 0000000..bc26e54 --- /dev/null +++ b/enums-gson/src/test/kotlin/pl/brightinventions/codified/gson/StringEnumListWrapper.kt @@ -0,0 +1,7 @@ +package pl.brightinventions.codified.gson + +import pl.brightinventions.codified.enums.CodifiedEnum + +data class StringEnumListWrapper( + val stringEnums: List> +) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f0ef10c..7057903 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,3 +2,4 @@ rootProject.name = "codified" include("enums") include("enums-serializer") include("enums-jackson") +include("enums-gson")