Proper serialization of ItemStack and other Serializable stuff in Bukkit API
This commit is contained in:
parent
da1ee9d882
commit
8b6fe63df1
@ -52,6 +52,7 @@ public class Json {
|
||||
private static Gson build(Function<GsonBuilder, GsonBuilder> builderModifier) {
|
||||
GsonBuilder base = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(new CustomAdapterFactory())
|
||||
.disableHtmlEscaping()
|
||||
.setLenient();
|
||||
return builderModifier.apply(base).create();
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package fr.pandacube.lib.paper.json;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.TreeTypeAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import fr.pandacube.lib.core.json.Json;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Gson adapter for ConfigurationSerializable, an interface implemented by several classes in the Bukkit API to ease
|
||||
* serialization to YAML.
|
||||
*
|
||||
* To not reinvent the wheel, this class uses the Bukkit’s Yaml API to convert the objects from/to json.
|
||||
*/
|
||||
/* package */ class ConfigurationSerializableAdapter implements JsonSerializer<ConfigurationSerializable>, JsonDeserializer<ConfigurationSerializable> {
|
||||
|
||||
public static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newTypeHierarchyFactory(ConfigurationSerializable.class, new ConfigurationSerializableAdapter());
|
||||
|
||||
private static final TypeToken<Map<String, Object>> MAP_STR_OBJ_TYPE = new TypeToken<>() { };
|
||||
|
||||
|
||||
private boolean isItemStack(Map<String, Object> deserializedMap) {
|
||||
return deserializedMap.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)
|
||||
&& deserializedMap.get(ConfigurationSerialization.SERIALIZED_TYPE_KEY) instanceof String serializedType
|
||||
&& ItemStack.class.equals(ConfigurationSerialization.getClassByAlias(serializedType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSerializable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonObject jsonObj) || !jsonObj.has(ConfigurationSerialization.SERIALIZED_TYPE_KEY))
|
||||
throw new JsonParseException("Unable to deserialize a ConfigurationSerializable from the provided json structure.");
|
||||
Map<String, Object> map = context.deserialize(jsonObj, MAP_STR_OBJ_TYPE.getType());
|
||||
if (isItemStack(map)) {
|
||||
ItemStackAdapter.fixDeserializationVersion(map);
|
||||
}
|
||||
String yaml = new Yaml().dump(Map.of("obj", map));
|
||||
YamlConfiguration cfg = new YamlConfiguration();
|
||||
try {
|
||||
cfg.loadFromString(yaml);
|
||||
} catch (InvalidConfigurationException e) {
|
||||
throw new JsonParseException("Unable t deserialize a ConfigurationSerializable from the provided json structure.", e);
|
||||
}
|
||||
return cfg.getSerializable("obj", ConfigurationSerializable.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ConfigurationSerializable src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.set("obj", src);
|
||||
Map<String, Object> map = new Yaml().load(cfg.saveToString());
|
||||
return context.serialize(map.get("obj"), MAP_STR_OBJ_TYPE.getType());
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
PaperJson.init();
|
||||
BlockVector bv = new BlockVector(12, 24, 48);
|
||||
String json = Json.gson.toJson(bv);
|
||||
System.out.println(json);
|
||||
BlockVector bv2 = Json.gson.fromJson(json, BlockVector.class);
|
||||
System.out.println(bv.equals(bv2));
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
package fr.pandacube.lib.paper.json;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
@ -10,7 +13,10 @@ import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.TreeTypeAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
@ -21,30 +27,62 @@ import java.util.Map;
|
||||
|
||||
private static final TypeToken<Map<String, Object>> MAP_STR_OBJ_TYPE = new TypeToken<>() { };
|
||||
|
||||
/** Gson instance with no custom type adapter */
|
||||
private static final Gson vanillaGson = new GsonBuilder().setLenient().create();
|
||||
|
||||
@Override
|
||||
public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
Map<String, Object> deserializedMap = context.deserialize(json, MAP_STR_OBJ_TYPE.getType());
|
||||
int itemStackVersion = deserializedMap.containsKey("v") ? ((Number)deserializedMap.get("v")).intValue() : -1;
|
||||
if (itemStackVersion >= 0) {
|
||||
@SuppressWarnings("deprecation")
|
||||
int currentDataVersion = Bukkit.getUnsafe().getDataVersion();
|
||||
if (itemStackVersion > currentDataVersion) {
|
||||
/* The itemStack we are deserializing is from a newer MC version, so Bukkit will refuse it.
|
||||
* We decide to ignore the provided version and consider that the received item stack is from current
|
||||
* version. We let Bukkit handles the deserialization with the data it can interpret, throwing an error
|
||||
* only if it can't.
|
||||
*/
|
||||
deserializedMap.put("v", currentDataVersion);
|
||||
return ItemStack.deserialize(deserializedMap);
|
||||
}
|
||||
if (!(json instanceof JsonObject jsonObj))
|
||||
throw new JsonParseException("Unable to deserialize a ConfigurationSerializable from the provided json structure.");
|
||||
if (jsonObj.has(ConfigurationSerialization.SERIALIZED_TYPE_KEY))
|
||||
return context.deserialize(jsonObj, ConfigurationSerializable.class);
|
||||
|
||||
|
||||
if (jsonObj.has("meta")
|
||||
&& jsonObj.get("meta") instanceof JsonObject metaJson
|
||||
&& !metaJson.has(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
|
||||
// item meta was serialized using GSON reflection serializer, instead of proper serialization using
|
||||
// ConfigurationSerializable interface. So we try to deserialize it the same way.
|
||||
|
||||
Map<String, Object> map = context.deserialize(jsonObj, MAP_STR_OBJ_TYPE.getType());
|
||||
fixDeserializationVersion(map);
|
||||
map.remove("meta");
|
||||
ItemStack is = ItemStack.deserialize(map);
|
||||
|
||||
Class<? extends ItemMeta> metaClass = is.getItemMeta().getClass();
|
||||
ItemMeta meta = vanillaGson.fromJson(jsonObj.get("meta"), metaClass);
|
||||
is.setItemMeta(meta);
|
||||
return is;
|
||||
}
|
||||
|
||||
return ItemStack.deserialize(deserializedMap);
|
||||
// deserialize using ConfigurationSerializableAdapter
|
||||
jsonObj.addProperty(ConfigurationSerialization.SERIALIZED_TYPE_KEY,
|
||||
ConfigurationSerialization.getAlias(ItemStack.class));
|
||||
return context.deserialize(jsonObj, ConfigurationSerializable.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return context.serialize(src.serialize(), MAP_STR_OBJ_TYPE.getType());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* package */ static void fixDeserializationVersion(Map<String, Object> deserializedMap) {
|
||||
if (!deserializedMap.containsKey("v"))
|
||||
return;
|
||||
int itemStackVersion = ((Number)deserializedMap.get("v")).intValue();
|
||||
if (itemStackVersion >= 0) {
|
||||
@SuppressWarnings("deprecation")
|
||||
int currentDataVersion = Bukkit.getUnsafe().getDataVersion();
|
||||
if (itemStackVersion > currentDataVersion) {
|
||||
/* Here, the itemStack we are deserializing is from a newer MC version, so Bukkit will refuse it.
|
||||
* We decide to ignore the provided version and consider that the received item stack is from current
|
||||
* version. We let Bukkit handles the deserialization with the data it can interpret, throwing an error
|
||||
* only if it can't.
|
||||
*/
|
||||
deserializedMap.put("v", currentDataVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ public class PaperJson {
|
||||
*/
|
||||
public static void init() {
|
||||
Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY);
|
||||
Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user