Skip to content

Commit

Permalink
Support JsonTreeWriter
Browse files Browse the repository at this point in the history
Uses internal APIs but this library isn't stable anyway. This works around that issue until a fixed future version of GSON that properly supports jsonValue in JsonTreeWriter (if ever) by converting it manually to a `JsonElement` and writing it directly via its `JsonElement` `TypeAdapter`.

Resolves #22
  • Loading branch information
ZacSweers committed Nov 18, 2020
1 parent 0d746fa commit 765e6e2
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 7 deletions.
55 changes: 50 additions & 5 deletions src/main/kotlin/com/slack/moshi/interop/gson/InteropBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.google.gson.JsonParser
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.internal.bind.JsonTreeWriter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.reflect.TypeToken
import com.slack.moshi.interop.gson.Serializer.GSON
import com.slack.moshi.interop.gson.Serializer.MOSHI
Expand Down Expand Up @@ -187,7 +189,7 @@ internal class GsonDelegatingJsonAdapter<T>(
private class MoshiGsonInteropTypeAdapterFactory(
private val interop: MoshiGsonInterop,
private val checkers: List<ClassChecker>,
private val logger: ((String) -> Unit)?,
private val logger: ((String) -> Unit)?
) : TypeAdapterFactory {
override fun <T> create(gson: Gson, typeToken: TypeToken<T>): TypeAdapter<T>? {
val type = typeToken.type
Expand All @@ -204,14 +206,22 @@ private class MoshiGsonInteropTypeAdapterFactory(
}

internal class MoshiDelegatingTypeAdapter<T>(
private val delegate: JsonAdapter<T>,
private val delegate: JsonAdapter<T>
) : TypeAdapter<T>() {
override fun write(writer: com.google.gson.stream.JsonWriter, value: T?) {
val serializedValue = delegate
val adjustedDelegate = delegate
.run { if (writer.serializeNulls) serializeNulls() else this }
.run { if (writer.isLenient) lenient() else this }
.toJson(value)
writer.jsonValue(serializedValue)
if (writer is JsonTreeWriter) {
// https://github.com/slackhq/moshi-gson-interop/issues/22
// Pending https://github.com/google/gson/pull/1819
val jsonValue = adjustedDelegate.toJsonValue(value)
val jsonElement = jsonValue.toJsonElement()
TypeAdapters.JSON_ELEMENT.write(writer, jsonElement)
} else {
val serializedValue = adjustedDelegate.toJson(value)
writer.jsonValue(serializedValue)
}
}

override fun read(reader: com.google.gson.stream.JsonReader): T? {
Expand Down Expand Up @@ -245,3 +255,38 @@ private fun JsonElement.toJsonValue(): Any? {
else -> error("Not possible")
}
}

private fun Any?.toJsonElement(): JsonElement {
return when (this) {
null -> JsonNull.INSTANCE
is Boolean -> JsonPrimitive(this)
is Char -> JsonPrimitive(this)
is Number -> JsonPrimitive(this)
is String -> JsonPrimitive(this)
is Array<*> -> {
JsonArray(size).apply {
for (element in this) {
add(element.toJsonElement())
}
}
}
is Collection<*> -> {
JsonArray(size).apply {
for (element in this@toJsonElement) {
add(element.toJsonElement())
}
}
}
is Map<*, *> -> {
JsonObject().apply {
for ((k, v) in entries) {
check(k is String) {
"JSON only supports String keys!"
}
add(k, v.toJsonElement())
}
}
}
else -> error("Unrecognized JsonValue type: $this")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.google.common.truth.Truth.assertThat
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.annotations.SerializedName
import com.google.gson.internal.bind.JsonTreeWriter
import com.google.gson.reflect.TypeToken
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
Expand Down Expand Up @@ -279,6 +280,41 @@ class MoshiGsonInteropTest {
.toJson(GsonNumber(value = Double.NaN))
assertThat(withNaN).isEqualTo("""{"value":NaN}""")
}

@Test
fun jsonTreeWriter_simple() {
val writer = JsonTreeWriter()
val moshiDelegate = moshi.adapter<Any>()
val typeAdapter = MoshiDelegatingTypeAdapter(moshiDelegate)
typeAdapter.write(writer, "yay")
assertThat(writer.get().toString()).isEqualTo("\"yay\"")
}

@Test
fun jsonTreeWriter_large() {
val writer = JsonTreeWriter()
val moshiDelegate = moshi.adapter<MoshiClass>()
val typeAdapter = MoshiDelegatingTypeAdapter(moshiDelegate)
val instance = MoshiClass(
SimpleMoshiClass("moshi!"),
SimpleGsonClass("gson!"),
RegularEnum.TYPE,
MoshiEnum.TYPE,
GsonEnum.TYPE,
listOf(SimpleMoshiClass("moshi!")),
listOf(SimpleGsonClass("gson!")),
SimpleGenericMoshiClass(MoshiEnum.TYPE),
SimpleGenericGsonClass(GsonEnum.TYPE),
SimpleMixedGenericGsonClass(SimpleGenericMoshiClass(GsonEnum.TYPE)),
SimpleMixedGenericMoshiClass(SimpleGenericGsonClass(MoshiEnum.TYPE))
)
typeAdapter.write(writer, instance)
val serialized = writer.get().toString()

// Now read the serialized one with a real instance and ensure they're equal
val secondInstance = moshi.adapter<MoshiClass>().fromJson(serialized)
assertThat(secondInstance).isEqualTo(instance)
}
}

@JsonClass(generateAdapter = true)
Expand All @@ -293,7 +329,7 @@ data class MoshiClass(
val genericMoshiClass: SimpleGenericMoshiClass<MoshiEnum>,
val genericGsonClass: SimpleGenericGsonClass<GsonEnum>,
val mixedGenericGsonClass: SimpleMixedGenericGsonClass<SimpleGenericMoshiClass<GsonEnum>>,
val mixedGenericMoshiClass: SimpleMixedGenericMoshiClass<SimpleGenericGsonClass<MoshiEnum>>
val mixedGenericMoshiClass: SimpleMixedGenericMoshiClass<SimpleGenericGsonClass<MoshiEnum>>,
)

data class GsonClass(
Expand All @@ -307,7 +343,7 @@ data class GsonClass(
val genericMoshiClass: SimpleGenericMoshiClass<MoshiEnum>,
val genericGsonClass: SimpleGenericGsonClass<GsonEnum>,
val mixedGenericGsonClass: SimpleMixedGenericGsonClass<SimpleGenericMoshiClass<GsonEnum>>,
val mixedGenericMoshiClass: SimpleMixedGenericMoshiClass<SimpleGenericGsonClass<MoshiEnum>>
val mixedGenericMoshiClass: SimpleMixedGenericMoshiClass<SimpleGenericGsonClass<MoshiEnum>>,
)

data class Complex(val value: ComplexNestedOne)
Expand Down

0 comments on commit 765e6e2

Please sign in to comment.