Jump to content

Welcome to DreamBot!

Download for Free

Supercharge Your Bots

Run unlimited bots today using DreamBot's Covert Mode and
stay more protected.

Upgrade Now
Frequently Asked Questions
  • Are you not able to open the client? Make sure you have Java 8 installed
  • 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 gold? You can purchase vouchers from other users
  • Try asking for help in the chatbox
OSRS Gambling

Interested in advertising your business? Reach out today!

Download the DreamBot client today!
Neffarion

Object Serialization/Deserialization to XML for SDN

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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...