Jump to content
Frequently Asked Questions
  • Are you not able to open the client? Try following our getting started guide
  • Still not working? Try downloading and running JarFix
  • Help! My bot doesn't do anything! Enable fresh start in client settings and restart the client
  • How to purchase with PayPal/OSRS/Crypto gold? You can purchase vouchers from other users
  • Try asking for help in the chatbox
  • Object Serialization/Deserialization to XML for SDN


    Neffarion
     Share

    Recommended Posts

    If you are looking into how to easily save/load object data and want to keep your script as lean as possible for SDN then hopefully this will help you learn something new.

    Why would you want to use serialization/deserialization ?
    The answer is simple. It allows you to save whole (or partial) Objects in your script to a file and load them back in easily on runtime. You can use this for saving/loading settings, sending information through the network or for local socket communication with other bots. There are a lot of uses for this.

     

     

     


    DATA STRUCTURE

     


    Let's start by making a few random classes to demonstrate how this works

    Food.java

    Spoiler
    package dreambot.tutorial.example;
    
    public class Food {
    
        private String name;
        private Integer weight;
        private Float volume;
        Type type;
    
        public Food(String name, Integer weight, Float volume){
            this.name = name;
            this.weight = weight;
            this.volume = volume;
        }
    
        public String getName(){
            return this.name;
        }
    
        public Integer getWeight(){
            return this.weight;
        }
    
        public Float getVolume(){
            return this.volume;
        }
    
        public Type getType(){
            return this.type;
        }
    
    }

     

     

    Orange.java

    Spoiler
    package dreambot.tutorial.example;
    
    public class Orange extends Food {
    
        public Orange(String name, Integer weight, Float volume) {
            super(name, weight, volume);
            this.type = Type.FRUIT;
        }
    
        public Orange(String name, Integer weight, Float volume, String origin) {
            this(name, weight, volume);
            this.origin = origin;
        }
    
        private String origin;
    
        public String getOrigin() {
            return origin;
        }
    
    }

     


    Carrot.java

    Spoiler
    package dreambot.tutorial.example;
    
    public class Carrot extends Food {
    
        public Carrot(String name, Integer weight, Float volume) {
            super(name, weight, volume);
            this.type = Type.VEGETABLE;
        }
    
        public Carrot(String name, Integer weight, Float volume, Integer length) {
            this(name, weight, volume);
            this.lenght = length;
        }
    
        private Integer length;
    
        public Integer getLength() {
            return length;
        }
    
    }

     

     

    Type.java

    Spoiler
    package dreambot.tutorial.example;
    
    public enum Type {
    
        FRUIT,
        VEGETABLE,
        MEAT,
        OTHER;
    
    }

     


    Then we have our main class just to run and show a simple example

    Tutorial.java

    Spoiler
    package dreambot.tutorial;
    
    import dreambot.tutorial.example.*;
    
    public final class Tutorial {
    
        public static void main(String... args){
            Food clementine = new Orange("Clementine", 127, 57.5f, "Algeria");
            Food tangerine = new Orange("Tangerine", 142, 61.2f);
            Food bolero = new Carrot("Bolero", 70, 150.5f);
        }
      
    }

     

     



    Okay now we have our basic structure for the data we want to save or load!

     

    For simplicity’s sake we only want to serialize/deserialize the Carrot "bolero"

    I will be showing you how to use JAXB (Java Architecture for XML Binding) for this. Why? It's simple and easy to learn. It's already built-in in Java and works totally fine. You don't need any extra libraries for this.

    Ok so first we need to annotate the classes which we want to serialize/deserialize. This is because without the annotations, JAXB doesn't know how to name things and what you want to serialize within the classes.
    We will add 5 different types of annotations to "Food.java" and "Type.java" because we want the information from those classes.
    (Read comments within code for explanation)

     


    ANNOTATIONS

     


    Food.java

    Spoiler
    package dreambot.tutorial.example;
    
    import javax.xml.bind.annotation.*;
    
    // The class name goes as the RootElement
    @XmlRootElement(name = "Food") 
      
    // This will set the mode which JAXB will choose what to serialize
    // We will go with NONE because its simpler and easier to understand
    @XmlAccessorType(value = XmlAccessType.NONE) 
      
    // This is optional. However, you should add this because it makes sure there are no duplicates when serializing. 
    // What I like to do is to set the name as the name of class and the namespace the actual java package
    @XmlType(name = "Food", namespace = "dreambot.tutorial.example") 
    public class Food {
    
        // Now we need to add the following annotation to each field which we want to be visible when serializing/deserializing
        @XmlElement(name = "name") 
        private String name;
    
        @XmlElement(name = "weight")
        private Integer weight;
    
        // Because we have XmlAccessType set to NONE and we didnt annotate this field, volume will never be "seen" and it gets ignored.
        private Float volume;
        
        @XmlElement(name = "type")
        Type type;
    
        public Food(String name, Integer weight, Float volume){
            this.name = name;
            this.weight = weight;
            this.volume = volume;
        }
    
        // You also need to add an empty constructor. THIS IS A MUST.
        public Food(){}
    
        public String getName(){
            return this.name;
        }
    
        public Integer getWeight(){
            return this.weight;
        }
    
        public Float getVolume(){
            return this.volume;
        }
    
        public Type getType(){
            return this.type;
        }
    
    }

     


    Type.java

    Spoiler
    package dreambot.tutorial.example;
    
    import javax.xml.bind.annotation.*;
    
    // Same as Food.java example
    @XmlType(name = "Type", namespace = "dreambot.tutorial.example")
    
    // Since we are dealing with an enum we will want to add this
    @XmlEnum
    public enum Type {
    
        FRUIT,
        VEGETABLE,
        MEAT,
        OTHER;
    
    }

     



     


    SERIALIZING

     



    In "Tutorial.java" add the following code to serialize the carrot we created

    Tutorial.java

    Spoiler
    package dreambot.tutorial;
    
    import java.util.*;
    import java.io.*;
    import javax.xml.bind.*;
    import dreambot.tutorial.example.*;
    
    public final class Tutorial {
    
        // Path for saving the object to. In this case we will just save it to a file in the desktop
        private static final String OUTPUT_PATH = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Food.xml";
    
        public static void main(String... args){
            Food clementine = new Orange("Clementine", 127, 57.5f, "Algeria");
            Food tangerine = new Orange("Tangerine", 142, 61.2f);
            Food bolero = new Carrot("Bolero", 70, 150.5f);
    
            Serialize(bolero);
        }
    
        private static void Serialize(Food food){
            File output = new File(OUTPUT_PATH);
            try{
                // Create a new JAXBContext instance with the class we want to work with. In this case Food, so we pass Food.class
                JAXBContext context = JAXBContext.newInstance(Food.class);
    
                // Create a Marshaller (it will convert the objects based on the context we made)
                Marshaller ms = context.createMarshaller();
                if(ms != null){
                    // Marshal/Serialize the "food" object into the file path we have chosen before
                    ms.marshal(food, output);
                }
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
        }

     

    The result?

    A simple XML file (Food.xml) with the data we had on the "bolero" carrot object

    Spoiler
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><Food><name>Bolero</name><weight>70</weight><type>VEGETABLE</type></Food>

     

    It looks kinda weird this way. This is because by default JAXB will remove any spaces to reduce file size. You can paste this text in https://www.samltool.com/prettyprint.php if you want to look how it is better structured.

    Pretty printed:

    Spoiler
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Food>
      <name>Bolero</name>
      <weight>70</weight>
      <type>VEGETABLE</type>
    </Food>

     

     

     

     

     


    DESERIALIZING

     


    Deserializing is basically the opposite of what we have done before. We take an XML file and JAXB will turn this into an object.


    To show how it works I have made a new XML file with the contents of the previous serialized one we made and changed the name and weight. Then we will deserialize and print on the console the object properties.

    Input.xml

    Spoiler
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><Food><name>Touchon</name><weight>64</weight><type>VEGETABLE</type></Food>

     


    Tutorial.java

    Spoiler
    package dreambot.tutorial;
    
    import java.util.*;
    import java.io.*;
    import javax.xml.bind.*;
    import dreambot.tutorial.example.*;
    
    public final class Tutorial {
    
        // Path for saving the object to. In this case we will just save it to a file in the desktop
        private static final String OUTPUT_PATH = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Food.xml";
    
        // Path to read object from
        private static final String INPUT_PATH = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Example.xml";
    
        public static void main(String... args){
            Food clementine = new Orange("Clementine", 127, 57.5f, "Algeria");
            Food tangerine = new Orange("Tangerine", 142, 61.2f);
            Food bolero = new Carrot("Bolero", 70, 150.5f, 18);
    
            Food myFood = Deserialize();
            System.out.format("Name: %s\nType: %s\nWeight: %d\n", myFood.getName(), myFood.getType().toString(), myFood.getWeight());
        }
    
        private static void Serialize(Food food){
            File output = new File(OUTPUT_PATH);
            try{
                JAXBContext context = JAXBContext.newInstance(Food.class);
                Marshaller ms = context.createMarshaller();
                if(ms != null){
                    ms.marshal(food, output);
                }
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
        }
    
        private static Food Deserialize(){
            File input = new File(INPUT_PATH);
            Food result = null;
            try{
                // Same as serialization. We need a JAXB Context with the class we want to work with
                JAXBContext context = JAXBContext.newInstance(Food.class);
    
                // Now we create an unmarshaller with the context made
                Unmarshaller ms = context.createUnmarshaller();
                if(ms != null){
                    // We extract the object from the file using the unmarshaller (AKA: Deserializing)
                    // You need to cast the object to (Food) otherwise you will just get an Object
                    result = (Food)ms.unmarshal(input);
                }
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
    
            return result;
        }
    }

     

    Console output:

    Name: Touchon
    Type: VEGETABLE
    Weight: 64g



     

     


    COLLECTIONS

     


    What if we want to serialize/deserialize a list of objects?


    To serialize a list of foods we are going to need to make a new Class to wrap the list of foods in.

    FoodCollection.java

    Spoiler
    package dreambot.tutorial.example;
    
    import java.util.List;
    import javax.xml.bind.annotation.*;
    
    @XmlRootElement(name = "FoodCollection")
    @XmlAccessorType(value = XmlAccessType.NONE)
    @XmlType(name = "FoodCollection", namespace = "dreambot.tutorial.example")
    public class FoodCollection {
        
        // We will need to add this annotation to wrap all the entries in the list
        @XmlElementWrapper(name="foods")
        @XmlElement(name="food")
        private List<Food> collection;
    
        public FoodCollection(List<Food> foods) {
            this.collection = foods;
        }
    
        public FoodCollection(){}
    
        public List<Food> getCollection() {
            return collection;
        }
    
        public void setCollection(List<Food> collection) {
            this.collection = collection;
        }
    
    }

     


    Now we make the changes to Tutorial.java (for both Serializing and Deserializing)

    Tutorial.java

    Spoiler
    package dreambot.tutorial;
    
    import java.util.*;
    import java.io.*;
    import javax.xml.bind.*;
    import dreambot.tutorial.example.*;
    
    public final class Tutorial {
    
        // Path for saving the objects to. In this case we will just save to a file in the desktop
        private static final String OUTPUT_PATH = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Food.xml";
    
        // Path to read object from
        private static final String INPUT_PATH = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Example.xml";
    
        public static void main(String... args){
            Food clementine = new Orange("Clementine", 127, 57.5f, "Algeria");
            Food tangerine = new Orange("Tangerine", 142, 61.2f);
            Food bolero = new Carrot("Bolero", 70, 150.5f, 18);
    
            List<Food> collection = Arrays.asList(clementine, tangerine, bolero);
            SerializeFoods(collection);
        }
    
        private static void Serialize(Food food){
            File output = new File(OUTPUT_PATH);
            try{
                JAXBContext context = JAXBContext.newInstance(Food.class);
                Marshaller ms = context.createMarshaller();
                if(ms != null){
                    ms.marshal(food, output);
                }
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
        }
    
        private static Food Deserialize(){
            File input = new File(INPUT_PATH);
            Food result = null;
            try{
                JAXBContext context = JAXBContext.newInstance(Food.class);
                Unmarshaller ms = context.createUnmarshaller();
                if(ms != null){
                    result = (Food)ms.unmarshal(input);
                }
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
    
            return result;
        }
    
    
        private static void SerializeFoods(List<Food> foods){
            File output = new File(OUTPUT_PATH);
            FoodCollection fc = new FoodCollection(foods);
    
            try{
                JAXBContext context = JAXBContext.newInstance(FoodCollection.class);
                Marshaller ms = context.createMarshaller();
                if(ms != null){
                    ms.marshal(fc, output);
                }
    
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
            
        }
    
        private static List<Food> DeserializeFoods(){
            File file = new File(INPUT_PATH);
            FoodCollection fc = null;
    
            try{
                JAXBContext context = JAXBContext.newInstance(FoodCollection.class);
                Unmarshaller ms = context.createUnmarshaller();
                if(ms != null){
                    fc = (FoodCollection) ms.unmarshal(file);
                }
    
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
    
            return fc.getCollection();
        }

     

     

    You should get something of sorts:

    Spoiler
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <FoodCollection>
    	<foods>
    		<food>
    			<name>Clementine</name>
    			<weight>127</weight>
    			<type>FRUIT</type>
    		</food>
    		<food>
    			<name>Tangerine</name>
    			<weight>142</weight>
    			<type>FRUIT</type>
    		</food>
    		<food>
    			<name>Bolero</name>
    			<weight>70</weight>
    			<type>VEGETABLE</type>
    		</food>
    	</foods>
    </FoodCollection>

     


     

     


    COMPRESSION

     


    But Neff! XML Files can get very large! Their file-size makes me want to almost puke!

    You need not worry! Because of the nature of XML structure, you can get very impressive file-size reductions with just using GZIP compression.
    How do you do that? Easy!
     

    Spoiler
    private static void CompressedSerialization(List<Food> foods){
            File output = new File(OUTPUT_PATH);
            FoodCollection fc = new FoodCollection(foods);
    
            try{
                JAXBContext context = JAXBContext.newInstance(FoodCollection.class);
                Marshaller ms = context.createMarshaller();
                if(ms != null){
                    try(FileOutputStream fs = new FileOutputStream(output, false)){
                        try(GZIPOutputStream gz = new GZIPOutputStream(fs)){
                            ms.marshal(fc, gz);
                        }
                    }catch(IOException io){
                        System.err.println(io.toString());
                    }
                }
    
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
            
        }

     

    Obviously you can't open the file with a text editor anymore (It's in a compressed file format), it won't make any sense.
    However, if you have 7-Zip installed for instance, you can just open the file and see the original XML text inside

    To deserialize you are going to have to use GZipInputStream instead and it will work fine.

    Spoiler
        private static List<Food> CompressedDeserializeFoods(){
            File file = new File(INPUT_PATH);
            FoodCollection fc = null;
    
            try{
                JAXBContext context = JAXBContext.newInstance(FoodCollection.class);
                Unmarshaller ms = context.createUnmarshaller();
                if(ms != null){
                    try(FileInputStream fs = new FileInputStream(file)){
                        try(GZIPInputStream gz = new GZIPInputStream(fs)){
                            fc = (FoodCollection)ms.unmarshal(gz);
                        }
                    }catch(IOException io){
                        System.err.println(io.toString());
                    }
                }
    
            }catch(JAXBException ex){
                System.err.println(ex.toString());
            }
            
            return fc.getCollection();
        }

     

     


     


     


    More JAXB annotation Information: https://howtodoinjava.com/jaxb/jaxb-annotations

    Q&A

    Q: Why don't you just use the default Java binary serialization?
    A: If you are going to use your script on the SDN, your script gets obfuscated. This means that your script classes are going to get randomly renamed every time you request to compile your script.
    This will make it so that if you serialize an object into a file and then upload a new script version and it gets compiled, the newer version will not be able to deserialize the previous serialized data.

    Q: Why don't you just use a JSON serialization library? Like GSON or Jackson?
    A: You can't use an external JAR library in the SDN. Also, if you upload the whole library source it will bloat the script quite a bit, making things worse and unnecessary.
    By using what this tutorial explains, you will only need to add some annotations. Everything else is built within Java 8 and doesn't need any additional libraries.

    Edited by Neffarion
    Typos
    Link to comment
    Share on other sites

    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now
     Share

    ×
    ×
    • Create New...

    Important Information

    We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.