Resource [Tutorial] Bukkit custom serialization

Discussion in 'Spigot Plugin Development' started by snowyCoder, May 19, 2016.

?

Do you find tutorials like this useful?

  1. Yes

  2. Maybe, With something improved

  3. No

Results are only viewable after voting.
  1. I thought there was already a tutorial out here for the bukkit serialization, but when I tried to search it there was nothing, so here it is.

    The bukkit serialization is a powerful API that permits bukkit/spigot to save and load classes without too much code.
    We can see it already applied in Vector, if we want to get an example of the api usage we can decompile the class and see what bukkit did.
    You can do it yourself but for the lazy people like me here it is:
    Code (Text):
    @SerializableAs("Vector")
    public class Vector implements Cloneable, ConfigurationSerializable {
    .....

       public Map<String, Object> serialize() {
            LinkedHashMap result = new LinkedHashMap();
            result.put("x", Double.valueOf(this.getX()));
            result.put("y", Double.valueOf(this.getY()));
            result.put("z", Double.valueOf(this.getZ()));
            return result;
        }

       public static Vector deserialize(Map<String, Object> args) {
            double x = 0.0D;
            double y = 0.0D;
            double z = 0.0D;
            if(args.containsKey("x")) {
                x = ((Double)args.get("x")).doubleValue();
            }

            if(args.containsKey("y")) {
                y = ((Double)args.get("y")).doubleValue();
            }

            if(args.containsKey("z")) {
                z = ((Double)args.get("z")).doubleValue();
            }

            return new Vector(x, y, z);
        }
    }
     
    Ok, so we can see three main things in here:
    firsly, the "implements ConfigurationSerializable", infact ConfigurationSerializable is a functional interface that has only the method "Map<String, Object> serialize()", your object needs to save all his data into this map.

    The second things that we can see is the method "deserialize(Map<String, Object>)", it will be used from bukkit when it finds some data of this class and wants to decode it.
    the static method "deserialize" is one way to deserialize objects: bukkit tries to deserialzie object with this methods (in order):
    1 - static method deserialize(Map<String, Object>)
    2 - static method valueOf(?)
    3 - constrctor (Map<String, Object>)
    When it tries to deserialize an object he firstly tries the first method and if it fails it goes to the second, and so on.

    The last thing that we can see here is the "@SerializableAs(String)" annotation, this isn't strictly necessair but if you don't want to have all the packet in the yml you must have this set.
    Made simple: when bukkit writes an object in the yml he must write the calss where the data comes, he firstly searches for this annotation and, if it's present, it uses the string put as input. In the case that it's absent it uses the class full path (ex: "==: org.bukkit.util.Vector" in place of "==:Vector").

    There's a fourth thing that can't be seen from this class, it's the registration part.
    Somewhere in the bukkit code you will find something like this
    Code (Text):
    ConfigurationSerialization.registerClass(Vector.class, "Vector");
    .
    this can't be in the Vector class because it has to be executed before bukkit/spigot loads its files.
    I usually put those registrations in the static block in the main class of my plugin.
    like this:
    Code (Text):
    public class Plugin extends JavaPlugin {
        static {
            ConfigurationSerialization.registerClass(Class1.class, "Class1");
            ConfigurationSerialization.registerClass(Class2.class, "SecondClass");
        }
    }

    after all this is set up you can put and get your classes from the bukkit serialization system just like a Vector or a List.

    i'll put an example class down here with even some uses:

    Code (Text):
    public class Test extends JavaPlugin {
        static {
            ConfigurationSerialization.registerClass(TestData.class, "TestData");
        }

        @Override
        public void onEnable() {
            FileConfiguration config = new YamlConfiguration();
            config.set("data", new TestData(314, "Bukkit serialization is the best way!"));
            try {
                config.save("testFile.yml");
            } catch (IOException e) {
                e.printStackTrace();
            }

            config = YamlConfiguration.loadConfiguration(new File("anotherTestFile.yml"));
            TestData data = (TestData) config.get("data");
            System.out.println("counter: " + data.counter + ", sentence:" + data.sentenceOfTheDay);
        }

        @SerializableAs("TestData")
        public static class TestData implements ConfigurationSerializable{

            private int counter;
            public String sentenceOfTheDay;

            public TestData(int counter, String sentenceOfTheDay) {
                this.counter = counter;
                this.sentenceOfTheDay = sentenceOfTheDay;
            }

            public TestData(Map<String, Object> map) {
                this.counter = (int) map.get("counter");
                this.sentenceOfTheDay = (String) map.get("sentenceOfTheDay");
            }

            public void increment() {
                counter++;
            }

            @Override
            public Map<String, Object> serialize() {
                Map<String, Object> map = new HashMap<>();
                map.put("counter", counter);
                map.put("sentenceOfTheDay", sentenceOfTheDay);
                return map;
            }

            public static TestData deserialize(Map<String, Object> map) {
                return new TestData((int)map.get("counter"), (String)map.get("sentenceOfTheDay"));
            }
        }
    }
    Let me know for improvements or errors to fix.
    Thanks for reading and sorry for any eventual mistake!
     
    #1 snowyCoder, May 19, 2016
    Last edited: May 20, 2016
    • Useful Useful x 16
    • Like Like x 5
    • Informative Informative x 5
  2. I think most people who really need to serialize classes, just make their own methods to keep their configs clean and controlled (at least I do). But, you are right about the lack of tutorials on serialization so this was definitely helpful :)
     
    • Like Like x 2
  3. Choco

    Moderator

    Hm. I knew there was a Serialization API, however I never really learned how to do it. This is a useful tutorial for those that want to store data within a .yml file quickly. Didn't realize this was the proper way to do it (I usually just create my own .serialize() and deserialize() methods). Thank you for the tutorial :) Great job.

    (Though you seem to have accidentally put a :eek: face in there when typing ==org.bukkit.util.Vector ;))
     
    • Agree Agree x 1
  4. How does this look in your config? Readable for humans? Or more like a random string?
     
  5. Choco

    Moderator

    That all depends on what you put in the serialization map ;)
     
  6. Thanks, fixed.

    If you use names that are readable for humans your config will be aswell, it's the principal purpose of the yaml (.yml) system.
    the class that i've put as example would produce something like this:
    Code (Text):
    data:
        counter: 314
        sentenceOfTheDay: 'Bukkit serialization is the best way!'
     
    • Like Like x 1
  7. I make custom serialization systems too sometimes, but i think that using yaml is great for things that can be modified by humans (without an external tool). There are different systems for different purposes, of course no one will use yaml to store worlds.
     
  8. This might come in handy later, thank you very much!
     
  9. Oh, I thought maybe the object gets serialized to something unreadable, but it literally puts in the config what you give it. Nice.
     
    • Agree Agree x 1
  10. This post is quite old, but I read in an another tutorial that you shouldn't put this in a static method, because it won't get called after a reload
     
  11. As everybody sais, reload tend to break plugins.. i don’t know if what you say is true, but people just should use restarts. Also man... you shouldn’t have posted it this thread is about 2 years old...
     
    • Agree Agree x 1
  12. Good point but a reload shouldn't clear anything from ConfigurationSerialization, only plugins and related things. I know this is quite a hack (as is the reload command, by the way) but it should work anyways.