贺兰兰
使用 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字段,即可解决问题

想牵着你的手丶
学废了。。

Sssss...
贺兰兰 发表于 2021-8-11 18:35
Kotlin好评(
正好我前几周遇到这个问题了
解决方案是不要序列化和反序列化ItemStack,而是直接序列化和反 ...

我想用这种方法把ItemStack换成字符串储存起来,但是遇到了上面一样的StackOverflow报错。
如果说只要把物品转换成字符串,“在数据对象的序列化器中序列化ItemStack字段”具体应该怎么操作呢?

贺兰兰
Sssss... 发表于 2021-8-21 16:05
我想用这种方法把ItemStack换成字符串储存起来,但是遇到了上面一样的StackOverflow报错。
如果说只要把 ...

https://github.com/shaokeyibb/Ra ... GiftSerializer.java

这有个示例你可以看一下

op1571206500
单纯建议,不同意勿杠!没有用过Gson,但是看了你的介绍我感觉这个时候还是fastjson比较方便了

Bryan33
本帖最后由 Bryan33 于 2021-8-29 23:03 编辑
贺兰兰 发表于 2021-8-8 13:56
不完全对,实际上 ItemMeta 的本质就是 NBT,这种 BukkitAPI 提供的 NBT 实现是可以被序列化的
但自定义 ...

ItemMeta自带实现的序列化中会把非标准nbt以base64编码储存在internal字段中

此外我一般纯储存ItemStack时都是通过Protocollib的com.comphenix.protocol.utility.StreamSerializer中的方法进行储存,不过这样不可读

小罗过的开心
帖主,yyds,真的太有用了

SumCraft
正好需要一个序列化的工具

迷离丶不羁
本帖最后由 迷离丶不羁 于 2021-9-6 10:12 编辑

BukkitAPI已经自带的有ItemStack序列化与反序列化功能了

    public static String saveItemStacks(ItemStack itemStack) throws IOException {

        String key = getRandomId();

        File itemStackConfigFile = new File(CustomStone.DATA_FOLDER + File.separator + "itemStackData.yml");
        YamlConfiguration itemStackConfig = YamlConfiguration.loadConfiguration(itemStackSourceFile);
        
        itemStackConfig.set(key, itemStack);
        itemStackConfig.save(itemStackConfigFile);

        ItemStack data = itemStackConfig.getItemStack(key);
        ItemMeta itemMeta = data.getItemMeta();

        return key;
    }
存储后的效果 :原版弓

'05865848':
  ==: org.bukkit.inventory.ItemStack
  type: BOW



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

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

堆栈溢出异常,应该是存在对象相互引用造成死循环,比如A对象里有个B对象,B对象里又有个变量指向A对象

Gjf.
6666666666666

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