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

[FEATURE] New factory API to control the type labelKey serialization. #1595

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ import javax.annotation.CheckReturnValue
* * If [withFallbackJsonAdapter] is used, then the `fallbackJsonAdapter.toJson(writer, value)` result will be returned.
* * Otherwise a [IllegalArgumentException] will be thrown.
*
* If all known subtypes has serializable type property in the class definition, then the factory can be configured
* with [autoSerializeLabel] to disable automatic serialization of the type label, and it's respective value.
*
* If the same subtype has multiple labels the first one is used when encoding.
*/
public class PolymorphicJsonAdapterFactory<T> internal constructor(
Expand All @@ -104,6 +107,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
private val labels: List<String>,
private val subtypes: List<Type>,
private val fallbackJsonAdapter: JsonAdapter<Any>?,
private val autoSerializeLabel: Boolean,
) : Factory {
/** Returns a new factory that decodes instances of `subtype`. */
public fun withSubtype(subtype: Class<out T>, label: String): PolymorphicJsonAdapterFactory<T> {
Expand All @@ -122,6 +126,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
labels = newLabels,
subtypes = newSubtypes,
fallbackJsonAdapter = fallbackJsonAdapter,
autoSerializeLabel = autoSerializeLabel,
)
}

Expand All @@ -141,6 +146,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
labels = labels,
subtypes = subtypes,
fallbackJsonAdapter = fallbackJsonAdapter,
autoSerializeLabel = autoSerializeLabel,
)
}

Expand All @@ -152,6 +158,21 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
return withFallbackJsonAdapter(buildFallbackJsonAdapter(defaultValue))
}

/**
* Allows the factory to be configured to enable/disable [labelKey] and it's value serialization
* into JSON object. By default, automatic serialization of type label key and value is enabled.
*/
public fun autoSerializeLabel(shouldAutoSerializeLabel: Boolean): PolymorphicJsonAdapterFactory<T> {
return PolymorphicJsonAdapterFactory(
baseType = baseType,
labelKey = labelKey,
labels = labels,
subtypes = subtypes,
fallbackJsonAdapter = fallbackJsonAdapter,
autoSerializeLabel = shouldAutoSerializeLabel
)
}

private fun buildFallbackJsonAdapter(defaultValue: T?): JsonAdapter<Any> {
return object : JsonAdapter<Any>() {
override fun fromJson(reader: JsonReader): Any? {
Expand All @@ -172,7 +193,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
return null
}
val jsonAdapters: List<JsonAdapter<Any>> = subtypes.map(moshi::adapter)
return PolymorphicJsonAdapter(labelKey, labels, subtypes, jsonAdapters, fallbackJsonAdapter)
return PolymorphicJsonAdapter(labelKey, labels, subtypes, jsonAdapters, fallbackJsonAdapter, autoSerializeLabel)
.nullSafe()
}

Expand All @@ -182,6 +203,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
private val subtypes: List<Type>,
private val jsonAdapters: List<JsonAdapter<Any>>,
private val fallbackJsonAdapter: JsonAdapter<Any>?,
private val autoSerializeLabel: Boolean,
) : JsonAdapter<Any>() {
/** Single-element options containing the label's key only. */
private val labelKeyOptions: Options = Options.of(labelKey)
Expand Down Expand Up @@ -231,7 +253,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
jsonAdapters[labelIndex]
}
writer.beginObject()
if (adapter !== fallbackJsonAdapter) {
if (autoSerializeLabel && adapter !== fallbackJsonAdapter) {
writer.name(labelKey).value(labels[labelIndex])
}
val flattenToken = writer.beginFlatten()
Expand Down Expand Up @@ -260,6 +282,7 @@ public class PolymorphicJsonAdapterFactory<T> internal constructor(
labels = emptyList(),
subtypes = emptyList(),
fallbackJsonAdapter = null,
autoSerializeLabel = true,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,49 @@ public void failOnUnknownMissingTypeLabel() throws IOException {
assertThat(decoded.value).isEqualTo("Okay!");
}

@Test
public void toJsonAutoTypeSerializationOn() throws IOException {
Moshi moshi =
new Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Message.class, "type")
.withSubtype(Success.class, "success")
.withSubtype(MessageWithType.class, "customType"))
.build();
JsonAdapter<Message> adapter = moshi.adapter(Message.class);

assertThat(adapter.toJson(new Success("Okay!")))
.isEqualTo("{\"type\":\"success\",\"value\":\"Okay!\"}");

// Ensures that when the `type` label auto serialization is on,
// it does continue to serialize the `type` along with class property `type` (twice)
assertThat(adapter.toJson(new MessageWithType("customType", "Object with type property")))
.isEqualTo(
"{\"type\":\"customType\",\"type\":\"customType\",\"value\":\"Object with type property\"}");
}

@Test
public void toJsonAutoTypeSerializationOff() throws IOException {
Moshi moshi =
new Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Message.class, "type")
.withSubtype(Success.class, "success")
.withSubtype(MessageWithType.class, "customType")
.autoSerializeLabel(false))
.build();
JsonAdapter<Message> adapter = moshi.adapter(Message.class);

// Validates that when `type` label auto serialization is off,
// it does NOT serialize the type label key and value
assertThat(adapter.toJson(new Success("Okay!"))).isEqualTo("{\"value\":\"Okay!\"}");

// Validates that when the `type` label auto serialization is off,
// it continues to serialize the class property `type` but not from the factory
assertThat(adapter.toJson(new MessageWithType("customType", "Object with type property")))
.isEqualTo("{\"type\":\"customType\",\"value\":\"Object with type property\"}");
}

interface Message {}

static final class Success implements Message {
Expand Down