From 942682ed21c4e839592233e0a6ea9def97a57b27 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Wed, 30 Oct 2024 18:42:13 +0000 Subject: [PATCH] feature(serializer-json, serializer-gson): Optionally emit type field for JSON serializers --- .../gson/ComponentSerializerImpl.java | 25 +++++++- .../json/JSONComponentConstants.java | 2 + .../text/serializer/json/JSONOptions.java | 14 +++++ .../json/JSONComponentSerializerTest.java | 63 +++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ComponentSerializerImpl.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ComponentSerializerImpl.java index c5c768af6..eca6125e8 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ComponentSerializerImpl.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/ComponentSerializerImpl.java @@ -70,23 +70,31 @@ import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.SELECTOR; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.SEPARATOR; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TEXT; +import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TRANSLATABLE; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TRANSLATE; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TRANSLATE_FALLBACK; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TRANSLATE_WITH; +import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.TYPE; final class ComponentSerializerImpl extends TypeAdapter { static final Type COMPONENT_LIST_TYPE = new TypeToken>() {}.getType(); static final Type TRANSLATABLE_ARGUMENT_LIST_TYPE = new TypeToken>() {}.getType(); static TypeAdapter create(final OptionState features, final Gson gson) { - return new ComponentSerializerImpl(features.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT), gson).nullSafe(); + return new ComponentSerializerImpl( + features.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT), + features.value(JSONOptions.EMIT_COMPONENT_TYPE), + gson + ).nullSafe(); } private final boolean emitCompactTextComponent; + private final boolean emitComponentType; private final Gson gson; - private ComponentSerializerImpl(final boolean emitCompactTextComponent, final Gson gson) { + private ComponentSerializerImpl(final boolean emitCompactTextComponent, final boolean emitComponentType, final Gson gson) { this.emitCompactTextComponent = emitCompactTextComponent; + this.emitComponentType = emitComponentType; this.gson = gson; } @@ -264,9 +272,11 @@ public void write(final JsonWriter out, final Component value) throws IOExceptio } if (value instanceof TextComponent) { + this.optionallyAddTypeName(out, TEXT); out.name(TEXT); out.value(((TextComponent) value).content()); } else if (value instanceof TranslatableComponent) { + this.optionallyAddTypeName(out, TRANSLATABLE); final TranslatableComponent translatable = (TranslatableComponent) value; out.name(TRANSLATE); out.value(translatable.key()); @@ -280,6 +290,7 @@ public void write(final JsonWriter out, final Component value) throws IOExceptio this.gson.toJson(translatable.arguments(), TRANSLATABLE_ARGUMENT_LIST_TYPE, out); } } else if (value instanceof ScoreComponent) { + this.optionallyAddTypeName(out, SCORE); final ScoreComponent score = (ScoreComponent) value; out.name(SCORE); out.beginObject(); @@ -293,14 +304,17 @@ public void write(final JsonWriter out, final Component value) throws IOExceptio } out.endObject(); } else if (value instanceof SelectorComponent) { + this.optionallyAddTypeName(out, SELECTOR); final SelectorComponent selector = (SelectorComponent) value; out.name(SELECTOR); out.value(selector.pattern()); this.serializeSeparator(out, selector.separator()); } else if (value instanceof KeybindComponent) { + this.optionallyAddTypeName(out, KEYBIND); out.name(KEYBIND); out.value(((KeybindComponent) value).keybind()); } else if (value instanceof NBTComponent) { + this.optionallyAddTypeName(out, NBT); final NBTComponent nbt = (NBTComponent) value; out.name(NBT); out.value(nbt.nbtPath()); @@ -326,6 +340,13 @@ public void write(final JsonWriter out, final Component value) throws IOExceptio out.endObject(); } + private void optionallyAddTypeName(final JsonWriter out, final String name) throws IOException { + if (this.emitComponentType) { + out.name(TYPE); + out.value(name); + } + } + private void serializeSeparator(final JsonWriter out, final @Nullable Component separator) throws IOException { if (separator != null) { out.name(SEPARATOR); diff --git a/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONComponentConstants.java b/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONComponentConstants.java index a353820a6..20326fb56 100644 --- a/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONComponentConstants.java +++ b/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONComponentConstants.java @@ -66,6 +66,8 @@ public final class JSONComponentConstants { public static final String SHOW_ITEM_COUNT = "count"; public static final @Deprecated String SHOW_ITEM_TAG = "tag"; public static final String SHOW_ITEM_COMPONENTS = "components"; + public static final String TYPE = "type"; + public static final String TRANSLATABLE = "translatable"; private JSONComponentConstants() { throw new IllegalStateException("Cannot instantiate"); diff --git a/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONOptions.java b/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONOptions.java index 05c1e1581..f08b612a0 100644 --- a/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONOptions.java +++ b/text-serializer-json/src/main/java/net/kyori/adventure/text/serializer/json/JSONOptions.java @@ -52,6 +52,7 @@ private JSONOptions() { * @sinceMinecraft 1.16 */ public static final Option EMIT_RGB = Option.booleanOption(key("emit/rgb"), true); + /** * Control how hover event values should be emitted. * @@ -83,6 +84,7 @@ private JSONOptions() { * @since 4.15.0 */ public static final Option VALIDATE_STRICT_EVENTS = Option.booleanOption(key("validate/strict_events"), true); + /** * Whether to emit the default hover event item stack quantity of {@code 1}. * @@ -99,6 +101,18 @@ private JSONOptions() { */ public static final Option SHOW_ITEM_HOVER_DATA_MODE = Option.enumOption(key("emit/show_item_hover_data"), ShowItemHoverDataMode.class, ShowItemHoverDataMode.EMIT_EITHER); + /** + * Whether to emit the type of the component. + * + *

If {@link #EMIT_COMPACT_TEXT_COMPONENT} is `true`, the component type will not be emitted for compact text components.

+ * + *

The client does not require the type field.

+ * + * @since 4.18.0 + * @sinceMinecraft 1.20.3 + */ + public static final Option EMIT_COMPONENT_TYPE = Option.booleanOption(key("emit/component_type"), false); + /** * Versioned by world data version. */ diff --git a/text-serializer-json/src/testFixtures/java/net/kyori/adventure/text/serializer/json/JSONComponentSerializerTest.java b/text-serializer-json/src/testFixtures/java/net/kyori/adventure/text/serializer/json/JSONComponentSerializerTest.java index 646f92c5c..9e264d8da 100644 --- a/text-serializer-json/src/testFixtures/java/net/kyori/adventure/text/serializer/json/JSONComponentSerializerTest.java +++ b/text-serializer-json/src/testFixtures/java/net/kyori/adventure/text/serializer/json/JSONComponentSerializerTest.java @@ -26,6 +26,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonParseException; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -98,4 +99,66 @@ void testFailOnInvalidHoverEvents() { }); } + @Test + void testComponentType() { + final JSONComponentSerializer serializer = JSONComponentSerializer + .builder() + .editOptions((builder) -> builder.value(JSONOptions.EMIT_COMPONENT_TYPE, true)) + .build(); + + // Emit-compact text is on by default, so this should not include the type. + assertEquals("\"hi\"", serializer.serialize(Component.text("hi"))); + this.testObject( + serializer, + Component.text("hi", NamedTextColor.RED), + object -> { + object.addProperty(JSONComponentConstants.TEXT, "hi"); + object.addProperty(JSONComponentConstants.COLOR, NamedTextColor.RED.toString()); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.TEXT); + } + ); + this.testObject( + serializer, + Component.translatable("hi"), + object -> { + object.addProperty(JSONComponentConstants.TRANSLATE, "hi"); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.TRANSLATABLE); + } + ); + this.testObject( + serializer, + Component.score("me", "ow"), + object -> { + object.add(JSONComponentConstants.SCORE, object(inner -> { + inner.addProperty(JSONComponentConstants.SCORE_NAME, "me"); + inner.addProperty(JSONComponentConstants.SCORE_OBJECTIVE, "ow"); + })); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.SCORE); + } + ); + this.testObject( + serializer, + Component.selector("hi"), + object -> { + object.addProperty(JSONComponentConstants.SELECTOR, "hi"); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.SELECTOR); + } + ); + this.testObject( + serializer, + Component.selector("hi"), + object -> { + object.addProperty(JSONComponentConstants.SELECTOR, "hi"); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.SELECTOR); + } + ); + this.testObject( + serializer, + Component.keybind("hi"), + object -> { + object.addProperty(JSONComponentConstants.KEYBIND, "hi"); + object.addProperty(JSONComponentConstants.TYPE, JSONComponentConstants.KEYBIND); + } + ); + } }