Util GsonFactory - Gson that works on ItemStack/PotionEffect/Location objects

Discussion in 'Resources' started by RingOfStorms, Dec 26, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    RingOfStorms

    Introduction

    For anyone that doesn't know what gson is, it is Google's open source Java library to serialize and deserialize Java objects to and from JSON. JSON is arguably much better than YAML and gson makes things pretty sweet to work with.

    Now for anyone who has used gson, they know that not all things can be serialized without custom TypeAdapters that help gson serialize and deserialize objects that it is unable to automatically do.

    Unfortunately for us, gson is unable to serialize/deserialize objects like ItemStack, PortionEffect, along with Location. Fortunately for you however, I've created a class that will require no work from what you would normally do but it supports those objects.

    GsonFactory

    Normally when you use gson, you simply do "new Gson().to/fromJson(...)". With this utility you will use the gson instances that are created in the class rather than making your own instance. These instances are lazily initialized so you can throw this util in any project and it won't use up much of anything when it is not used.

    The util contains two main instances of Gson that you will use. The two differences are the pretty vs compact gson instances. The difference between pretty and compact when printing to console or saving is the following:

    Pretty vs Compact
    getPrettyGson: (Prints in several easy to read lines with indentation)
    Code:json
    1.  
    2. {
    3. "TIME_ZONE_ID": "EST",
    4. "TIME_FORMAT": "MMMMM d, yyyy",
    5. "SQL_USER": "root",
    6. "SQL_PASS": "exmpass",
    7. "SQL_PORT": "3306",
    8. "SQL_HOST": "localhost",
    9. "SQL_DATABASE": "example"
    10. }
    11.  

    getCompactGson (Prints in one line)
    Code:json
    1.  
    2. {"TIME_ZONE_ID": "EST","TIME_FORMAT": "MMMMM d, yyyy","SQL_USER": "root","SQL_PASS": "exmpass","SQL_PORT": "3306","SQL_HOST": "localhost","SQL_DATABASE": "example"}
    3.  


    Serialization and Deserialization
    To serialize using GsonFactory you simply call:
    Code:java
    1.  
    2. GsonFactory.getPrettyGson().toJson(yourObjectHere);
    3. // or
    4. GsonFactory.getCompactGson().toJson(yourObjectHere);
    5.  

    And to deserialize it is:
    Code:java
    1.  
    2. YourObjectClass yourObjectHere = GsonFactory.getCompactGson().fromJson(/*the json string*/, YourObjectClass)
    3.  

    (for deserialization it doesn't matter if you use pretty or compact)

    Exclusion Strategy
    If you want a field in your class to be ignored, there is an exclusion strategy included in GsonFactory and it is annotation based. Inside of the class you want a field ignored, simply add @GsonIgnore before the field and GsonFactory will not serialize/deserialize that field.

    Example:
    The player object will not get serialized by GsonFactory, but the ItemStack array will.
    Code:java
    1.  
    2. @GsonIgnore
    3. private final Player player;
    4. private ItemStack[] arrayOfItems;
    5.  


    Util Code

    To use GsonFactory simply copy and paste it to your project and call GsonFactory.getPretty/CompactGson() from anywhere!

    Note: GsonFactory disables html escaping, in most cases this is to your benefit as it will make strings more readable for your end users, if you remove this option then your json will be safe in html, but things like the '&' and section characters turn into the ugly unicode codes that look like '\u0026".

    Gist for code
    Code:java
    1.  
    2. //TODO add your package name here
    3.  
    4. import com.google.gson.ExclusionStrategy;
    5. import com.google.gson.FieldAttributes;
    6. import com.google.gson.Gson;
    7. import com.google.gson.GsonBuilder;
    8. import com.google.gson.TypeAdapter;
    9. import com.google.gson.annotations.Expose;
    10. import com.google.gson.reflect.TypeToken;
    11. import com.google.gson.stream.JsonReader;
    12. import com.google.gson.stream.JsonToken;
    13. import com.google.gson.stream.JsonWriter;
    14. import java.io.IOException;
    15. import java.lang.annotation.ElementType;
    16. import java.lang.annotation.Retention;
    17. import java.lang.annotation.RetentionPolicy;
    18. import java.lang.annotation.Target;
    19. import java.lang.reflect.Type;
    20. import java.util.Date;
    21. import java.util.HashMap;
    22. import java.util.Map;
    23. import java.util.Map.Entry;
    24. import net.minecraft.server.v1_8_R3.MojangsonParseException;
    25. import net.minecraft.server.v1_8_R3.MojangsonParser;
    26. import net.minecraft.server.v1_8_R3.NBTBase;
    27. import net.minecraft.server.v1_8_R3.NBTTagCompound;
    28. import org.bukkit.Bukkit;
    29. import org.bukkit.ChatColor;
    30. import org.bukkit.Location;
    31. import org.bukkit.Material;
    32. import org.bukkit.World;
    33. import org.bukkit.configuration.serialization.ConfigurationSerializable;
    34. import org.bukkit.configuration.serialization.ConfigurationSerialization;
    35. import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
    36. import org.bukkit.craftbukkit.v1_8_R3.util.CraftMagicNumbers;
    37. import org.bukkit.inventory.ItemStack;
    38. import org.bukkit.inventory.meta.ItemMeta;
    39. import org.bukkit.potion.PotionEffect;
    40. import org.bukkit.potion.PotionEffectType;
    41.  
    42. /* *
    43. * Created by Joshua Bell (RingOfStorms)
    44. *
    45. * Post explaining here: [URL]http://bukkit.org/threads/gsonfactory-gson-that-works-on-itemstack-potioneffect-location-objects.331161/[/URL]
    46. * */
    47. public class GsonFactory {
    48.  
    49. @Retention(RetentionPolicy.RUNTIME)
    50. @Target({ElementType.FIELD})
    51. public static @interface Ignore {}
    52.  
    53. /*
    54.   - I want to not use Bukkit parsing for most objects... it's kind of clunky
    55.   - Instead... I want to start using any of Mojang's tags
    56.   - They're really well documented + built into MC, and handled by them.
    57.   - Rather than kill your old code, I'm going to write TypeAdapaters using Mojang's stuff.
    58.   */
    59.  
    60. private static Gson g = new Gson();
    61.  
    62. private final static String CLASS_KEY = "SERIAL-ADAPTER-CLASS-KEY";
    63.  
    64. private static Gson prettyGson;
    65. private static Gson compactGson;
    66.  
    67. /**
    68.   * Returns a Gson instance for use anywhere with new line pretty printing
    69.   * <p>
    70.   * Use @GsonIgnore in order to skip serialization and deserialization
    71.   * </p>
    72.   * @return a Gson instance
    73.   */
    74. public static Gson getPrettyGson () {
    75. if (prettyGson == null)
    76. prettyGson = new GsonBuilder().addSerializationExclusionStrategy(new ExposeExlusion())
    77. .addDeserializationExclusionStrategy(new ExposeExlusion())
    78. .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackGsonAdapter())
    79. .registerTypeAdapter(PotionEffect.class, new PotionEffectGsonAdapter())
    80. .registerTypeAdapter(Location.class, new LocationGsonAdapter())
    81. .registerTypeAdapter(Date.class, new DateGsonAdapter())
    82. .setPrettyPrinting()
    83. .disableHtmlEscaping()
    84. .create();
    85. return prettyGson;
    86. }
    87.  
    88. /**
    89.   * Returns a Gson instance for use anywhere with one line strings
    90.   * <p>
    91.   * Use @GsonIgnore in order to skip serialization and deserialization
    92.   * </p>
    93.   * @return a Gson instance
    94.   */
    95. public static Gson getCompactGson () {
    96. if(compactGson == null)
    97. compactGson = new GsonBuilder().addSerializationExclusionStrategy(new ExposeExlusion())
    98. .addDeserializationExclusionStrategy(new ExposeExlusion())
    99. .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackGsonAdapter())
    100. .registerTypeAdapter(PotionEffect.class, new PotionEffectGsonAdapter())
    101. .registerTypeAdapter(Location.class, new LocationGsonAdapter())
    102. .registerTypeAdapter(Date.class, new DateGsonAdapter())
    103. .disableHtmlEscaping()
    104. .create();
    105. return compactGson;
    106. }
    107.  
    108. /**
    109.   * Creates a new instance of Gson for use anywhere
    110.   * <p>
    111.   * Use @GsonIgnore in order to skip serialization and deserialization
    112.   * </p>
    113.   * @return a Gson instance
    114.   */
    115. public static Gson getNewGson(boolean prettyPrinting) {
    116. GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(new ExposeExlusion())
    117. .addDeserializationExclusionStrategy(new ExposeExlusion())
    118. .registerTypeHierarchyAdapter(ItemStack.class, new NewItemStackAdapter())
    119. .disableHtmlEscaping();
    120. if (prettyPrinting)
    121. builder.setPrettyPrinting();
    122. return builder.create();
    123. }
    124.  
    125. private static Map<String, Object> recursiveSerialization (ConfigurationSerializable o) {
    126. Map<String, Object> originalMap = o.serialize();
    127. Map<String, Object> map = new HashMap<String, Object>();
    128. for(Entry<String, Object> entry : originalMap.entrySet()) {
    129. Object o2 = entry.getValue();
    130. if(o2 instanceof ConfigurationSerializable) {
    131. ConfigurationSerializable serializable = (ConfigurationSerializable) o2;
    132. Map<String, Object> newMap = recursiveSerialization(serializable);
    133. newMap.put(CLASS_KEY, ConfigurationSerialization.getAlias(serializable.getClass()));
    134. map.put(entry.getKey(), newMap);
    135. }
    136. }
    137. map.put(CLASS_KEY, ConfigurationSerialization.getAlias(o.getClass()));
    138. return map;
    139. }
    140.  
    141. private static Map<String, Object> recursiveDoubleToInteger (Map<String, Object> originalMap) {
    142. Map<String, Object> map = new HashMap<String, Object>();
    143. for(Entry<String, Object> entry : originalMap.entrySet()) {
    144. Object o = entry.getValue();
    145. if(o instanceof Double) {
    146. Double d = (Double) o;
    147. Integer i = d.intValue();
    148. map.put(entry.getKey(), i);
    149. }else if(o instanceof Map) {
    150. Map<String, Object> subMap = (Map<String, Object>) o;
    151. map.put(entry.getKey(), recursiveDoubleToInteger(subMap));
    152. }else{
    153. map.put(entry.getKey(), o);
    154. }
    155. }
    156. return map;
    157. }
    158.  
    159. private static class ExposeExlusion implements ExclusionStrategy {
    160. @Override
    161. public boolean shouldSkipField(FieldAttributes fieldAttributes) {
    162. final Ignore ignore = fieldAttributes.getAnnotation(Ignore.class);
    163. if(ignore != null)
    164. return true;
    165. final Expose expose = fieldAttributes.getAnnotation(Expose.class);
    166. return expose != null && (!expose.serialize() || !expose.deserialize());
    167. }
    168.  
    169. @Override
    170. public boolean shouldSkipClass(Class<?> aClass) {
    171. return false;
    172. }
    173. }
    174.  
    175. private static String nbtToString(NBTBase base) {
    176. return base.toString().replace(",}", "}").replace(",]", "]").replaceAll("[0-9]+\\:", "");
    177. }
    178.  
    179. private static net.minecraft.server.v1_8_R3.ItemStack removeSlot(ItemStack item) {
    180. if (item == null)
    181. return null;
    182. net.minecraft.server.v1_8_R3.ItemStack nmsi = CraftItemStack.asNMSCopy(item);
    183. if (nmsi == null)
    184. return null;
    185. NBTTagCompound nbtt = nmsi.getTag();
    186. if (nbtt != null) {
    187. nbtt.remove("Slot");
    188. nmsi.setTag(nbtt);
    189. }
    190. return nmsi;
    191. }
    192.  
    193. private static ItemStack removeSlotNBT (ItemStack item) {
    194. if (item == null)
    195. return null;
    196. net.minecraft.server.v1_8_R3.ItemStack nmsi = CraftItemStack.asNMSCopy(item);
    197. if (nmsi == null)
    198. return null;
    199. NBTTagCompound nbtt = nmsi.getTag();
    200. if(nbtt != null) {
    201. nbtt.remove("Slot");
    202. nmsi.setTag(nbtt);
    203. }
    204. return CraftItemStack.asBukkitCopy(nmsi);
    205. }
    206.  
    207. private static class NewItemStackAdapter extends TypeAdapter<ItemStack> {
    208. @Override
    209. public void write(JsonWriter jsonWriter, ItemStack itemStack) throws IOException {
    210. if (itemStack == null) {
    211. jsonWriter.nullValue();
    212. return;
    213. }
    214. net.minecraft.server.v1_8_R3.ItemStack item = removeSlot(itemStack);
    215. if (item == null) {
    216. jsonWriter.nullValue();
    217. return;
    218. }
    219. try {
    220. jsonWriter.beginObject();
    221. jsonWriter.name("type");
    222.  
    223. jsonWriter.value(itemStack.getType().toString()); //I hate using this - but
    224. jsonWriter.name("amount");
    225.  
    226. jsonWriter.value(itemStack.getAmount());
    227. jsonWriter.name("data");
    228.  
    229.  
    230. jsonWriter.value(itemStack.getDurability());
    231. jsonWriter.name("tag");
    232.  
    233. if (item != null && item.getTag() != null) {
    234. jsonWriter.value(nbtToString(item.getTag()));
    235. } else
    236. jsonWriter.value("");
    237. jsonWriter.endObject();
    238. } catch (Exception ex) {
    239. ex.printStackTrace();
    240. }
    241. }
    242.  
    243. @Override
    244. public ItemStack read(JsonReader jsonReader) throws IOException {
    245. if (jsonReader.peek() == JsonToken.NULL) {
    246. return null;
    247. }
    248. jsonReader.beginObject();
    249. jsonReader.nextName();
    250. Material type = Material.getMaterial(jsonReader.nextString());
    251. jsonReader.nextName();
    252. int amount = jsonReader.nextInt();
    253. jsonReader.nextName();
    254. int data = jsonReader.nextInt();
    255. net.minecraft.server.v1_8_R3.ItemStack item = new net.minecraft.server.v1_8_R3.ItemStack(CraftMagicNumbers.getItem(type), amount, data);
    256. jsonReader.nextName();
    257. String next = jsonReader.nextString();
    258. if (next.startsWith("{")) {
    259. NBTTagCompound compound = null;
    260. try {
    261. compound = MojangsonParser.parse(ChatColor.translateAlternateColorCodes('&', next));
    262. } catch (MojangsonParseException e) {
    263. e.printStackTrace();
    264. }
    265. item.setTag(compound);
    266. }
    267. jsonReader.endObject();
    268. return CraftItemStack.asBukkitCopy(item);
    269. }
    270. }
    271.  
    272. private static class ItemStackGsonAdapter extends TypeAdapter<ItemStack> {
    273.  
    274. private static Type seriType = new TypeToken<Map<String, Object>>(){}.getType();
    275.  
    276. @Override
    277. public void write(JsonWriter jsonWriter, ItemStack itemStack) throws IOException {
    278. if(itemStack == null) {
    279. jsonWriter.nullValue();
    280. return;
    281. }
    282. jsonWriter.value(getRaw(removeSlotNBT(itemStack)));
    283. }
    284.  
    285. @Override
    286. public ItemStack read(JsonReader jsonReader) throws IOException {
    287. if(jsonReader.peek() == JsonToken.NULL) {
    288. jsonReader.nextNull();
    289. return null;
    290. }
    291. return fromRaw(jsonReader.nextString());
    292. }
    293.  
    294. private String getRaw (ItemStack item) {
    295. Map<String, Object> serial = item.serialize();
    296.  
    297. if(serial.get("meta") != null) {
    298. ItemMeta itemMeta = item.getItemMeta();
    299.  
    300. Map<String, Object> originalMeta = itemMeta.serialize();
    301. Map<String, Object> meta = new HashMap<String, Object>();
    302. for(Entry<String, Object> entry : originalMeta.entrySet())
    303. meta.put(entry.getKey(), entry.getValue());
    304. Object o;
    305. for(Entry<String, Object> entry : meta.entrySet()) {
    306. o = entry.getValue();
    307. if(o instanceof ConfigurationSerializable) {
    308. ConfigurationSerializable serializable = (ConfigurationSerializable) o;
    309. Map<String, Object> serialized = recursiveSerialization(serializable);
    310. meta.put(entry.getKey(), serialized);
    311. }
    312. }
    313. serial.put("meta", meta);
    314. }
    315.  
    316. return g.toJson(serial);
    317. }
    318.  
    319. private ItemStack fromRaw (String raw) {
    320. Map<String, Object> keys = g.fromJson(raw, seriType);
    321.  
    322. if(keys.get("amount") != null) {
    323. Double d = (Double) keys.get("amount");
    324. Integer i = d.intValue();
    325. keys.put("amount", i);
    326. }
    327.  
    328. ItemStack item;
    329. try {
    330. item = ItemStack.deserialize(keys);
    331. }catch(Exception e) {
    332. return null;
    333. }
    334.  
    335. if(item == null)
    336. return null;
    337.  
    338. if(keys.containsKey("meta")) {
    339. Map<String, Object> itemmeta = (Map<String, Object>) keys.get("meta");
    340. itemmeta = recursiveDoubleToInteger(itemmeta);
    341. ItemMeta meta = (ItemMeta) ConfigurationSerialization.deserializeObject(itemmeta, ConfigurationSerialization.getClassByAlias("ItemMeta"));
    342. item.setItemMeta(meta);
    343. }
    344.  
    345. return item;
    346. }
    347. }
    348.  
    349. private static class PotionEffectGsonAdapter extends TypeAdapter<PotionEffect> {
    350.  
    351. private static Type seriType = new TypeToken<Map<String, Object>>(){}.getType();
    352.  
    353. private static String TYPE = "effect";
    354. private static String DURATION = "duration";
    355. private static String AMPLIFIER = "amplifier";
    356. private static String AMBIENT = "ambient";
    357.  
    358. @Override
    359. public void write(JsonWriter jsonWriter, PotionEffect potionEffect) throws IOException {
    360. if(potionEffect == null) {
    361. jsonWriter.nullValue();
    362. return;
    363. }
    364. jsonWriter.value(getRaw(potionEffect));
    365. }
    366.  
    367. @Override
    368. public PotionEffect read(JsonReader jsonReader) throws IOException {
    369. if(jsonReader.peek() == JsonToken.NULL) {
    370. jsonReader.nextNull();
    371. return null;
    372. }
    373. return fromRaw(jsonReader.nextString());
    374. }
    375.  
    376. private String getRaw (PotionEffect potion) {
    377. Map<String, Object> serial = potion.serialize();
    378.  
    379. return g.toJson(serial);
    380. }
    381.  
    382. private PotionEffect fromRaw (String raw) {
    383. Map<String, Object> keys = g.fromJson(raw, seriType);
    384. return new PotionEffect(PotionEffectType.getById(((Double) keys.get(TYPE)).intValue()), ((Double) keys.get(DURATION)).intValue(), ((Double) keys.get(AMPLIFIER)).intValue(), (Boolean) keys.get(AMBIENT));
    385. }
    386. }
    387.  
    388. private static class LocationGsonAdapter extends TypeAdapter<Location> {
    389.  
    390. private static Type seriType = new TypeToken<Map<String, Object>>(){}.getType();
    391.  
    392. private static String UUID = "uuid";
    393. private static String X = "x";
    394. private static String Y = "y";
    395. private static String Z = "z";
    396. private static String YAW = "yaw";
    397. private static String PITCH = "pitch";
    398.  
    399. @Override
    400. public void write(JsonWriter jsonWriter, Location location) throws IOException {
    401. if(location == null) {
    402. jsonWriter.nullValue();
    403. return;
    404. }
    405. jsonWriter.value(getRaw(location));
    406. }
    407.  
    408. @Override
    409. public Location read(JsonReader jsonReader) throws IOException {
    410. if(jsonReader.peek() == JsonToken.NULL) {
    411. jsonReader.nextNull();
    412. return null;
    413. }
    414. return fromRaw(jsonReader.nextString());
    415. }
    416.  
    417. private String getRaw (Location location) {
    418. Map<String, Object> serial = new HashMap<String, Object>();
    419. serial.put(UUID, location.getWorld().getUID().toString());
    420. serial.put(X, Double.toString(location.getX()));
    421. serial.put(Y, Double.toString(location.getY()));
    422. serial.put(Z, Double.toString(location.getZ()));
    423. serial.put(YAW, Float.toString(location.getYaw()));
    424. serial.put(PITCH, Float.toString(location.getPitch()));
    425. return g.toJson(serial);
    426. }
    427.  
    428. private Location fromRaw (String raw) {
    429. Map<String, Object> keys = g.fromJson(raw, seriType);
    430. World w = Bukkit.getWorld(java.util.UUID.fromString((String) keys.get(UUID)));
    431. return new Location(w, Double.parseDouble((String) keys.get(X)), Double.parseDouble((String) keys.get(Y)), Double.parseDouble((String) keys.get(Z)),
    432. Float.parseFloat((String) keys.get(YAW)), Float.parseFloat((String) keys.get(PITCH)));
    433. }
    434. }
    435.  
    436. private static class DateGsonAdapter extends TypeAdapter<Date> {
    437. @Override
    438. public void write(JsonWriter jsonWriter, Date date) throws IOException {
    439. if(date == null) {
    440. jsonWriter.nullValue();
    441. return;
    442. }
    443. jsonWriter.value(date.getTime());
    444. }
    445.  
    446. @Override
    447. public Date read(JsonReader jsonReader) throws IOException {
    448. if(jsonReader.peek() == JsonToken.NULL) {
    449. jsonReader.nextNull();
    450. return null;
    451. }
    452. return new Date(jsonReader.nextLong());
    453. }
    454. }
    455. }
    456.  
    457.  
     
    Last edited: Jul 23, 2015
    SirFaizdat, Goblom, Totom3 and 5 others like this.
  2. Offline

    DemKazoo

    This is pretty neat! Comes out very handy for my own API ;)
     
    ChipDev and RingOfStorms like this.
  3. Offline

    RingOfStorms

    Updated code to fix a reference to a class called ItemStackUtil that obviously isn't there
     
  4. Offline

    callum2904

    @RingOfStorms Update for 1.8_r3?
    Has an issue with some of the imports and when reimporting them there is still one error.
     
  5. Offline

    BillyGalbreath

    Bump.

    Would also like to see this updated to v1_8_R3
     
  6. Offline

    RingOfStorms

    BillyGalbreath likes this.
  7. Offline

    Tecno_Wizard

    I'm in the process of using json.simple and Gson to make a full Database API for bukkit instead of constantly loading slow YAML files.
     
    Last edited: Jun 22, 2015
  8. Offline

    RingOfStorms

    ok... and what does that have to do with this resource :confused:?
     
  9. Offline

    Tecno_Wizard

    Same API, same idea.
     
  10. Offline

    BillyGalbreath

    I'm having a problem creating json from player's inventory.

    Code:
    GsonFactory.getCompactJson().toJson(player.getInventory().getContents());
    
    Code:
    Caused by: java.lang.IllegalArgumentException: class net.minecraft.server.v1_8_R3.EntityItemFrame declares multiple JSON fields named c
    
     
  11. Offline

    RingOfStorms

    @BillyGalbreath After a few hours of frustrating wtf's at GSON I realized that the registering of the TypeAdapter is concrete to the top level, in this case it was ItemStack.class, so GSON was not using the Adapter for CraftItemStack, which is what is should of been doing. So alternatively we can switch it to a hierarchy which will apply the adapter to work on extending classes.

    I updated the code above with that one line change, at this point my current GsonFactory is very different from the one I have here, and I do realize there is bloat and weird comments on this version but I don't have time to clean it up.

    But you can just change the registerTypeAdapter for the itemstack class to registerTypeHierarchyAdapter in the current one you are using
     
    Last edited: Jul 23, 2015
  12. Offline

    SirFaizdat

    @RingOfStorms Absolutely amazing resource, but what is this licensed under?
     
Thread Status:
Not open for further replies.

Share This Page