Gson escapes all quote marks

Discussion in 'Spigot Plugin Development' started by MinecraftKnightz, Jun 23, 2018.

  1. Hi guys. I'm using Gson to serialize and deserialize an Inventory (including the ItemStack contents). It all works well, but all of the quote marks in the serialized ItemStack are escaped for some reason. Here's an example:
    Code (Text):
    {"contents":["{\"type\":\"JUNGLE_FENCE\",\"amount\":1,\"durability\":0}"],"title":"Testing","size":27}
     
    I'm not 100% accustomed to Json, so I'm not sure if this is necessary, but if not how can I disable it? My GsonBuilder is using prettyPrinting and disablingHtmlEscapes, although it doesn't seem to change anything.

    Thanks a lot guys.
     
  2. Tux

    Tux

    Can you show your code? It looks like you're putting serialized JSON into your object.
     
  3. It's quite long, but here you go:

    Code (Java):
    public class ItemStackJsonSerializer extends TypeAdapter<ItemStack> {
        private static final String MATERIAL = "type",
                AMOUNT = "amount", DURABILITY = "durability", META = "meta",
                NAME = "name", LORE = "lore",
                ENCHANTS = "enchants", ENCHANTMENT = "enchantment", LEVEL = "level", FLAGS = "flags", UNBREAKABLE = "unbreakable";

        @Override
        @SneakyThrows
        public void write(JsonWriter out, ItemStack value) {
            if (value == null) return;
            if (value.getType() == AIR) return;
            out.beginObject();
            out.name(MATERIAL).value(value
                    .getType().name());
            out.name(AMOUNT).value(value.getAmount());
            out.name(DURABILITY).value(value.getDurability());
            if (!value.getEnchantments().isEmpty()) {
                JsonWriter enchants = out
                        .name(ENCHANTS).beginArray();
                Map<Enchantment, Integer> e = value.getEnchantments();
                for (Map.Entry<Enchantment, Integer> ent : e.entrySet()) {
                    JsonWriter en = enchants.beginObject();
                    en.name(ENCHANTMENT).value(ent.getKey().getName());
                    en.name(LEVEL).value(ent.getValue());
                    en.endObject();
                }
                enchants.endArray();
            }
            if (value.hasItemMeta()) {
                JsonWriter meta = out.name(META).beginObject();
                ItemMeta im = value.getItemMeta();
                if (im.getDisplayName() != null) {
                    meta.name(NAME).value(im.getDisplayName());
                }
                if (im.isUnbreakable()) {
                    meta.name(UNBREAKABLE).value(true);
                }
                if (im.hasLore()) {
                    JsonWriter lore = meta.name(LORE).beginArray();
                    for (String s : im.getLore()) {
                        lore.value(s);
                    }
                    lore.endArray();
                }
                if (!im.getItemFlags().isEmpty()) {
                    JsonWriter flags = meta.name(FLAGS).beginArray();
                    for (ItemFlag i : im.getItemFlags()) {
                        flags.value(i.name());
                    }
                    flags.endArray();
                }
                meta.endObject();
            }
            out.endObject();
            out.flush();
            out.close();
        }
     
        @Override
        public ItemStack read(JsonReader in) throws IOException {
            Material type = AIR;
            int amount = 1;
            short durability = 0;
            boolean hasMeta = false;
            String name = null;
            List<String> lore = new ArrayList<>();
            List<ItemFlag> flags = new ArrayList<>();
            Map<Enchantment, Integer> enchants = new HashMap<>();
            boolean unbreakable = false;
            while (in.hasNext()) {
                switch (in.nextName()) {
                    case MATERIAL:
                        type = Material.valueOf(in.nextString());
                        break;
                    case AMOUNT:
                        amount = in.nextInt();
                        break;
                    case DURABILITY:
                        durability = (short) in.nextInt();
                        break;
                    case ENCHANTS:
                        in.beginArray();
                        while (in.hasNext()) {
                            in.beginObject();
                            String enchant = null;
                            int level = 0;
                            while (in.hasNext()) {
                                switch (in.nextName()) {
                                    case ENCHANTMENT:
                                        enchant = in.nextString();
                                        break;
                                    case LEVEL:
                                        level = in.nextInt();
                                        break;
                                }
                            }
                            enchants.put(Enchantment.getByName(enchant), level);
                            in.endObject();
                        }
                        break;
                    case META:
                        hasMeta = true;
                        in.beginObject();
                        while (in.hasNext()) {
                            String s = in.nextName();
                            switch (s) {
                                case NAME:
                                    name = in.nextString();
                                    break;
                                case LORE:
                                    in.beginArray();
                                    while (in.hasNext()) {
                                        lore.add(in.nextString());
                                    }
                                    in.endArray();

                                    break;
                                case FLAGS:
                                    in.beginArray();
                                    while (in.hasNext()) {
                                        flags.add(ItemFlag.valueOf(in.nextString()));
                                    }
                                    in.endArray();
                                    break;
                                case UNBREAKABLE:
                                    unbreakable = in.nextBoolean();
                                    break;
                            }
                        }
                        in.endObject();
                        break;
                }
            }
            ItemStack itemStack = new ItemStack(type, amount, durability);
            if (enchants != null) {
                enchants.forEach(itemStack::addEnchantment);
            }
            if (hasMeta && type != AIR) {
                ItemMeta meta = itemStack.getItemMeta();
                if (name != null) {
                    meta.setDisplayName(name);
                }
                if (lore != null) {
                    meta.setLore(lore);
                }
                if (flags != null) {
                    flags.forEach(meta::addItemFlags);
                }
                meta.setUnbreakable(unbreakable);
                itemStack.setItemMeta(meta);
            }
            return itemStack;
        }
    }
    Should be easy enough to understand, also in my case it's in an array of Inventory Contents

    @FloThePony Yeah, I was wondering as I tried essentially the same thing (or as close as I could get) with various other objects and it worked fine. Thanks though :D
     
  4. That's only the code (de)serializing the item stack, you didn't show us how you serialize actual inventories
     
  5. Code (Text):

    public class InventorySerializer extends TypeAdapter<Inventory> {

        @Override

        public void write(JsonWriter out, Inventory value) throws IOException {

            out.beginObject();

            JsonWriter contents = out.name("contents").beginArray();

            for (ItemStack i : value.getContents()) {

                if (i != null && i.getType() != AIR) {

                    contents.value(KnightzAPI.gson.getAdapter(ItemStack.class).toJson(i));

                }

            }

            contents.endArray();

            out.name("title").value(value.getTitle());

            out.name("size").value(value.getSize());

            out.endObject();

            out.flush();

            out.close();

        }


        @Override

        public Inventory read(JsonReader in) throws IOException {

            in.beginObject();

            List<ItemStack> contents = new ArrayList<>();

            String title = null;

            int size = 9;

            while (in.hasNext()) {

                switch (in.nextName()) {

                    case "contents":

                        in.beginArray();

                        while (in.hasNext()) {

                            contents.add(KnightzAPI.gson.fromJson(in.nextString(), ItemStack.class));

                        }

                        in.endArray();

                        break;

                    case "title":

                        title = in.nextString();

                        break;

                    case "size":

                        size = in.nextInt();

                        break;

                }

            }

            in.endObject();

            Inventory i = Bukkit.createInventory(null, size, title);

            i.setContents(contents.toArray(new ItemStack[]{}));

            return i;

        }


     
    Sorry, didn't deem it necessary as only the ItemStack was being escaped as you can see in OP.

    Sent from my SM-G903F using Tapatalk
     
  6. Tux

    Tux

    Yes, you are including serialized JSON in your array. Don't do that.

    Untested, but try this:

    Code (Java):

            JsonWriter contents = out.name("contents").beginArray();
            for (ItemStack i : value.getContents()) {
                if (i != null && i.getType() != AIR) {
                    KnightzAPI.gson.getAdapter(ItemStack.class).write(contents, i);
                }
            }
            contents.endArray();
     
    Also, for perfect inventory saving, you should definitely preserve null values.
     
  7. That's fixed it, thanks!
     
  8. New problem though - I have to get the TypeAdapter in order to serialize anything, otherwise it throws this exception:
    Code (Text):
    java.lang.IllegalArgumentException: class net.minecraft.server.v1_8_R3.EntityItemFrame declares multiple JSON fields named c
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:122) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:72) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.getAdapter(Gson.java:356) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:82) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:81) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:118) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:72) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.getAdapter(Gson.java:356) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ArrayTypeAdapter$1.create(ArrayTypeAdapter.java:48) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.getAdapter(Gson.java:356) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:82) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:81) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:118) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:72) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.getAdapter(Gson.java:356) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:55) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.toJson(Gson.java:593) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.toJson(Gson.java:572) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.toJson(Gson.java:527) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at com.google.gson.Gson.toJson(Gson.java:507) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at uk.knightz.bedwars.Bedwars.onEnable(Bedwars.java:128) ~[?:?]
            at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:321) ~[spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:340) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:405) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.loadPlugin(CraftServer.java:357) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.enablePlugins(CraftServer.java:317) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.reload(CraftServer.java:741) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.Bukkit.reload(Bukkit.java:535) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.command.defaults.ReloadCommand.execute(ReloadCommand.java:25) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:141) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.dispatchCommand(CraftServer.java:641) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.dispatchServerCommand(CraftServer.java:627) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at net.minecraft.server.v1_8_R3.DedicatedServer.aO(DedicatedServer.java:412) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at net.minecraft.server.v1_8_R3.DedicatedServer.B(DedicatedServer.java:375) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at net.minecraft.server.v1_8_R3.MinecraftServer.A(MinecraftServer.java:654) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at net.minecraft.server.v1_8_R3.MinecraftServer.run(MinecraftServer.java:557) [spigot-1.8.8.jar:git-Spigot-21fe707-e1ebe52]
            at java.lang.Thread.run(Unknown Source) [?:1.8.0_172]
     
    Obviously I can get the adapter as a workaround, but I then can't use prettyPrinting (or it doesn't do anything either way), which defeats the point as ideally the JSON is user friendly.

    To clarify, this works:
    Code (Java):
    System.out.println(KnightzAPI.gson.getAdapter(Inventory.class).toJson(someInventory));
    But this doesn't and throws the exception:
    Code (Java):
    System.out.println(KnightzAPI.gson.toJson(someInventory));
    Edit: I presume this is because the NMS ItemStack holds a reference to EntityItemFrame, although I don't know why the adapter isn't being registered with Gson
     
  9. Tux

    Tux

    Have you tried this?
     
  10. Yes
     
  11. You can just use the ConfigurationSerializable API to turn ItemStacks into Maps and back, which are ready to be dumped into Gson (or any format).

    You don't actually need to do the parsing yourself :)