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.
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
That's only the code (de)serializing the item stack, you didn't show us how you serialize actual inventories
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
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.
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
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