贺兰兰
使用 Gson 序列化和反序列化 org.bukkit.ItemStack

写这玩意的原因

昨天肝了一整天 这个插件,为了方便起见我希望使用 Json 来存储 ItemStack 信息,结果没想到为了序列化这个 ItemStack 花了我一整个下午的时间。在 StackOverFlow 和 SpigotMC 兜兜转转一大圈后,终于写出来了序列化代码。

正好刚刚逛论坛的时候,看到了同小组的 这个教程,心想正好他只写了 YAML,没有写 GSON,我就帮忙给他补充一下了233。

开始

探寻 ItemStack 序列化的实质

既然 Bukkit API 已经向我们提供了 ItemStack 的序列化和反序列化方法,那么就让我们深入一下这两个方法:

  1. // on ItemStack.class
  2.     @Utility
  3.     public Map<String, Object> serialize() {
  4.         Map<String, Object> result = new LinkedHashMap<String, Object>();

  5.         result.put("type", getType().name());

  6.         if (getDurability() != 0) {
  7.             result.put("damage", getDurability());
  8.         }

  9.         if (getAmount() != 1) {
  10.             result.put("amount", getAmount());
  11.         }

  12.         ItemMeta meta = getItemMeta();
  13.         if (!Bukkit.getItemFactory().equals(meta, null)) {
  14.             result.put("meta", meta);
  15.         }

  16.         return result;
  17.     }
复制代码
  1. // on ItemStack.class
  2.     public static ItemStack deserialize(Map<String, Object> args) {
  3.         Material type = Material.getMaterial((String) args.get("type"));
  4.         short damage = 0;
  5.         int amount = 1;

  6.         if (args.containsKey("damage")) {
  7.             damage = ((Number) args.get("damage")).shortValue();
  8.         }

  9.         if (args.containsKey("amount")) {
  10.             amount = ((Number) args.get("amount")).intValue();
  11.         }

  12.         ItemStack result = new ItemStack(type, amount, damage);

  13.         if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated
  14.             Object raw = args.get("enchantments");

  15.             if (raw instanceof Map) {
  16.                 Map<?, ?> map = (Map<?, ?>) raw;

  17.                 for (Map.Entry<?, ?> entry : map.entrySet()) {
  18.                     Enchantment enchantment = Enchantment.getByName(entry.getKey().toString());

  19.                     if ((enchantment != null) && (entry.getValue() instanceof Integer)) {
  20.                         result.addUnsafeEnchantment(enchantment, (Integer) entry.getValue());
  21.                     }
  22.                 }
  23.             }
  24.         } else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist
  25.             Object raw = args.get("meta");
  26.             if (raw instanceof ItemMeta) {
  27.                 result.setItemMeta((ItemMeta) raw);
  28.             }
  29.         }

  30.         return result;
  31.     }
复制代码

由此看来,就非常明了了:原来 ItemStack 的序列化就是将各种属性存储到一个 Map<String, Object> 里,那么我们只需要将这个 Map<String,Object> 通过 Gson 序列化为 Json,就解决问题啦!

配置 Gson 并自定义 Gson 序列化器

默认情况下,Gson 并不会调用 ItemStack 的序列化和反序列化方法,如果不调用这些方法而强行序列化,就会引发奇怪的报错。因此我们需要自定义 Gson 序列化器。因此,创建 ItemStackSerializer,并实现 JsonDeserializer<ItemStack>, JsonSerializer<ItemStack>

  1. public class ItemStackSerializer implements JsonDeserializer<ItemStack>, JsonSerializer<ItemStack> {
  2.     @Override
  3.     public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  4.         return ItemStack.deserialize(GsonBuilder().create().fromJson(json, new TypeToken<Map<String, Object>>() {}.getType()));
  5.     }

  6.     @Override
  7.     public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) {
  8.         return new GsonBuilder().create().toJsonTree(src.getItem().serialize());
  9.     }
  10. }
复制代码

然后,使用 GsonBuilder 生成一个注册了 ItemStack 序列化器的 Gson 对象:

  1. Gson gson = new GsonBuilder()
  2.             .enableComplexMapKeySerialization()
  3.             .serializeNulls()
  4.             .setPrettyPrinting()
  5.             .registerTypeAdapter(ItemStack.class, new ItemStackSerializer())
  6.             .create();
复制代码

这样一来,我们就可以使用 gson.fromJson 或是 gson.toJson 将 ItemStack 正确的序列化或是反序列化啦!

来自群组: Server CT

玄月月
荷兰好快荷兰好快荷兰好快荷兰好快荷兰好快荷兰好快

金瓯
萌新请问,这种方式对nbt属性也可以成功保存吗

洞穴夜莺
金瓯 发表于 2021-8-8 12:05
萌新请问,这种方式对nbt属性也可以成功保存吗

不能,,

贺兰兰

不完全对,实际上 ItemMeta 的本质就是 NBT,这种 BukkitAPI 提供的 NBT 实现是可以被序列化的
但自定义的 NBT 就不行了
(没有测试过高版本的持久化序列接口可否被序列化,个人猜测应该也是可以的)

HolographicHat
想问问为什么会出现这种错误https://paste.ubuntu.com/p/GhGcxs4Qbc/
  1. class ItemStackSerializer : JsonSerializer<ItemStack>,JsonDeserializer<ItemStack> {
  2.         override fun serialize(src: ItemStack, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
  3.             return Gson().toJsonTree(src.serialize())
  4.         }
  5.         override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ItemStack {
  6.             return ItemStack.deserialize(Gson().fromJson<Map<String,Any>>(json))
  7.         }
  8.         private inline fun <reified T> Gson.fromJson(json: JsonElement): T = this.fromJson(json, object: TypeToken<T>() {}.type)
  9.     }
复制代码

服务端paper1.16.5 git-465

贺兰兰
HolographicHat 发表于 2021-8-11 17:28
想问问为什么会出现这种错误https://paste.ubuntu.com/p/GhGcxs4Qbc/

服务端paper1.16.5 git-465 ...

Kotlin好评(
正好我前几周遇到这个问题了
解决方案是不要序列化和反序列化ItemStack,而是直接序列化和反序列化你的数据对象,在数据对象的序列化器中序列化ItemStack字段,即可解决问题

第一页 上一页 下一页 最后一页