Saving and Loading Data: XmlSerializer

  • What to expect
    Data can be saved in many different formats. Xml is one of the more standardized ones. There are several different ways to parse an xml file. The XmlSerializer is one of the easiest way included directly with every .NET/mono installation.

The whole tutorial is available at the unify wiki too http://www.unifycommunity.com/wiki/index.php?title=Saving_and_Loading_Data:_XmlSerializer

The goal of this tutorial will be to save and load data with the following xml structure.

<MonsterCollection>
	<Monsters>
		<Monster name="a">
			<Health>5</Health>
		</Monster>
		<Monster name="b">
			<Health>3</Health>
		</Monster>
	</Monsters>
</MonsterCollection>
  • This is the C# version for the Unityscript/Javascript version look here*

  • Preparing the Monster class

  • Monster.cs*

using System.Xml;
using System.Xml.Serialization;

public class Monster
{
	[XmlAttribute("name")]
	public string Name;
	
	public int Health;
}

The XmlSerializer automatically knows about each public variable or read/write property in any type you can throw at it. Primitive types like string, int, float and enums can be automatically serialized.

Through attributes you can further tell the XmlSerializer about how you want the xml to be parsed.
By default every variable will be translated to one xml element (e.g. 5).
If you want it to be parsed as attribute ( e.g. ) you have to use XmlAttribute(name) like in the sample.

  • The MonsterContainer
    To store all of the monsters we need a list of all of them.
using System.Collections.Generic;
using System.Xml;

[XmlRoot("MonsterCollection")]
public class MonsterContainer
{
	[XmlArray("Monsters"),XmlArrayItem("Monster")]
	public List<Monster> Monsters = new List<Monster>();
}

The root element of each xml file should be annotated with the XmlRoot attribute. This way the XmlSerializer will know which XmlElement is to be expected as the root element.
A list is just like an array with the added bonus of being able to add new elements easily.

With XmlArray and XmlArrayItem you can declare how the list should be represented within the xml file.

  • Reading data
var serializer = new XmlSerializer(typeof(MonsterContainer));
var stream = new FileStream(path, FileMode.Open);
var container = serializer.Deserialize(stream) as MonsterContainer;
stream.Close();
  • Writing data
var serializer = new XmlSerializer(typeof(MonsterContainer));
var stream = new FileStream(path, FileMode.Create));
serializer.Serialize(stream, this);
stream.Close();

Make sure you call the Close or Dispose method after writing else the file you just created will only be created in memory and will never be actually written to the file.

  • Convenience
    I personally like putting read and write methods in the root class like this.
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

[XmlRoot("MonsterCollection")]
public class MonsterContainer
{
	[XmlArray("Monsters"),XmlArrayItem("Monster")]
	public Monster[] Monsters;
	
	public void Save(string path)
	{
		var serializer = new XmlSerializer(typeof(MonsterContainer));
		using(var stream = new FileStream(path, FileMode.Create))
		{
			serializer.Serialize(stream, this);
		}
	}
	
	public static MonsterContainer Load(string path)
	{
		var serializer = new XmlSerializer(typeof(MonsterContainer));
		using(var stream = new FileStream(path, FileMode.Open))
		{
			return serializer.Deserialize(stream) as MonsterContainer;
		}
	}
}
  • Usage
  • Reading*
var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
  • Writing*
monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));

As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.

  • Application.dataPath points to your asset/project directory.
    If you have your xml file stored in your project at DataFiles/test/monsters.xml you can access it by using Path.Combine(Application.dataPath, “DataFiles/test/monsters.xml”);

  • Application.persistentDataPath points to a directory where your application can store user specific data on the target computer. This is a recommended way to store files locally for a user like highscores or savegames.

Additional Notes

  • You have to define at least one constructor without parameters for the XmlSerializer to work correctly
  • If your class contains a struct or class Variable that contains the supported data types the XmlSerializer can automatically serialize it too.
  • All fields and properties you want to serialize have to be public!
  • If you want to serialize a property both a getter and a setter have to be present

More information about the the xmlserializer and all its special features can be found at

Any feedback is welcome. Should I continue with other tutorials about reading and writing data(Textfiles, Binary files) and provide some samples for unityscript and boo?

4 Likes

This is nicely done, I’ll have to give this a try when I have the chance ! Thanks again!

Looks good.

Questions:

  • Does this also work with classes that extend monobehaviour? (pitfalls?)

Suggestions:

  • Put in some Sentence in regard to file storage for the final build.
    Otherwise people will ask themself why it’s not working in the build vs editor.

  • Put in some info on how to use the ressource folder? (advantage / disadvantage)

Thank you

Ps: looking forward to some more tutorials on File / IO Handling.

I don’t think it is possible as the XmlSerializer will create a new instance of an monobehaviour you have to add a component via AddComponent and there is currently no way(and probably never will be) to add a precreated MonoBehaviour to a GameObject.

You can add values to a MonoBehaviour and manually ( or using reflection as Unity) move all values to MonoBehaviour created through AddComponent but I would strongly discourage the use of monbehaviour because you should keep your runtime and save file structure as independed as possible. If you don’t do this things might break as you change your runtime structure. It might be some more work but I think it will save more than that whenever something changes ( Especially with most of the unity users heavily relying on the structure of a scene for it to work correctly. )

Nevertheless as some others are asking this too currently I will look into how XmlSerialization for a scene could work. ( but this will probably be a script sold through the asset store with a small howto posted in the teaching section.)

Unityscript Version

  • What to expect
    Data can be saved in many different formats. Xml is one of the more standardized ones. There are several different ways to parse an xml file. The XmlSerializer is one of the easiest way included directly with every .NET/mono installation.

The goal of this tutorial will be to save and load data with the following xml structure.

<MonsterCollection>
	<Monsters>
		<Monster name="a">
			<Health>5</Health>
		</Monster>
		<Monster name="b">
			<Health>3</Health>
		</Monster>
	</Monsters>
</MonsterCollection>
  • Preparing the Monster class
  • Monster.js*
import System.Xml;
import System.Xml.Serialization;

public class Monster
{
	@XmlAttribute("name")
	public var Name : String;
	
	public var Health : int;
}

The XmlSerializer automatically knows about each public variable or read/write property in any type you can throw at it. Primitive types like string, int, float and enums can be automatically serialized.

Through attributes you can further tell the XmlSerializer about how you want the xml to be parsed.
By default every variable will be translated to one xml element (e.g. 5).
If you want it to be parsed as attribute ( e.g. ) you have to use XmlAttribute(name) like in the sample.

  • The MonsterContainer
    To store all of the monsters we need a list of all of them.
import System.Collections.Generic;
import System.Xml;

@XmlRoot("MonsterCollection")
public class MonsterContainer
{
	@XmlArray("Monsters")
	@XmlArrayItem("Monster")
	public var Monsters : List.<Monster> = new List.<Monster>();
}

The root element of each xml file should be annotated with the XmlRoot attribute. This way the XmlSerializer will know which XmlElement is to be expected as the root element.
A list is just like an array with the added bonus of being able to add new elements easily.

With XmlArray and XmlArrayItem you can declare how the list should be represented within the xml file.

  • Reading data
var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
var stream : Stream = new FileStream(path, FileMode.Open);
var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
stream.Close();
  • Writing data
var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
var stream : Stream = new FileStream(path, FileMode.Create);
serializer.Serialize(stream, this);
stream.Close();

Make sure you call the Close or Dispose method after writing else the file you just created will only be created in memory and will never be actually written to the file.

  • Convenience
    I personally like putting read and write methods in the root class like this.
import System.Collections.Generic;
import System.Xml;
import System.IO;

@XmlRoot("MonsterCollection")
public class MonsterContainer
{
	@XmlArray("Monsters")
	@XmlArrayItem("Monster")
	public var Monsters : List.<Monster>;
	
	public function Save(path : String)
	{
		var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
		var stream : Stream = new FileStream(path, FileMode.Create);
		serializer.Serialize(stream, this);
		stream.Close();
	}
	
	public static function Load(path : String):MonsterContainer 
	{
		var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
		var stream : Stream = new FileStream(path, FileMode.Open);
		var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
		stream.Close();
		return result;
	}
}
  • Usage
  • Reading*
var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
  • Writing*
monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));

As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.

  • Application.dataPath points to your asset/project directory.
    If you have your xml file stored in your project at DataFiles/test/monsters.xml you can access it by using Path.Combine(Application.dataPath, “DataFiles/test/monsters.xml”);

  • Application.persistentDataPath points to a directory where your application can store user specific data on the target computer. This is a recommended way to store files locally for a user like highscores or savegames.

  • All fields and properties you want to serialize have to be public!

  • If you want to serialize a property both a getter and a setter have to be present

Additional Notes

  • You have to define at least one constructor without parameters for the XmlSerializer to work correctly
  • If your class contains a struct or class Variable that contains the supported data types the XmlSerializer can automatically serialize it too.

More information about the the xmlserializer and all its special features can be found at

Any feedback is welcome.

Could you post these tutorials on the Wiki? It’ll be easier to find that way. Thanks! :slight_smile:

http://www.unifycommunity.com/wiki/index.php?title=Saving_and_Loading_Data:_XmlSerializer

Nicely done! Thanks very much!

Does this work on the IOS system?

This was massively helpful to me and I thank you profusely. Personally I’m serializing into PlayerPrefs using this:

	public void Save() {
		XmlSerializer serializer = new XmlSerializer(typeof(Player));

		using (StringWriter writer = new StringWriter()) {
			serializer.Serialize(writer, this);
			PlayerPrefs.SetString("playerData", writer.ToString());
		}
	}
	
	public static Player Load() {
		XmlSerializer serializer = new XmlSerializer(typeof(Player));
		
		using (StringReader reader = new StringReader(PlayerPrefs.GetString("playerData"))) {
			if (PlayerPrefs.HasKey("playerData")) {
				return serializer.Deserialize(reader) as Player;
			} else {
				return new Player();
			}
		}		
	}

You got me 90% of the way there.

3 Likes

I’m getting a lot of errors with this. How do I adapt this to Unity-centric C#/Mono?

Add my “thank you” to the growing pile. =)

Nice, but System.IO can’t be incluedd in any web project for example.
How could i deserialize xml without System.IO ? (stringreader inside system.io)

Thanks

I am definitly using a webplayer to deserialize xml data through this method with the following code.

var stringReader = new StringReader(xml);
var xmlReader = new XmlTextReader(stringReader);
var xmlSerializer = new XmlSerializer(typeof(Config));
            
return (Config)xmlSerializer.Deserialize(xmlReader);

xml is a string coming from a file downloaded with WWW.

System.IO is available in the webplayer but some methods like everything that accesses the file system directly causes a SecurityException when used. Things like StringReader and Path can be safely used for every unity target.

Hy thanks for the sample.
It’s working very well on the editor but when i export on android it doesn’t work (can’t save the xml).

i currently using a list but if i delete it the xml is well saved.

any idea?

Can someone make a boo tutorial on this?

@EddieB: Where exactly are you saving the file to(Android only permits writes in a certain directories)? And how exactly doesn’t it work, an exception, doesn’t write, something else? To be honest I haven’t done much with unity android, only taking my experience from listening to my colleagues^^

@pananag: I added the basic usage samples for boo too. I’ll translate the rest too soon.

Hello!

Thanks for the tutorial! I got it reading the xml just fine!

I am however missing something very obvious on how to save the current monsters. For example, if I create:

		Monster myMonster = new Monster();
		myMonster.Name = "Big Ugly";
		myMonster.Health = 5;

How do I get it into the MonsterContainer to save? I know I am just missing something silly.

@matrix211v1
You just have to put the monster into the container.

var container = new MonsterContainer();
container.Monsters = new List<Monster>();
container.Monsters.Add(myMonster); 
container.Save(path)

if you don’t want to initialize the container each time you should change the following line

[XmlArray("Monsters"),XmlArrayItem("Monster")]
public List<Monster> Monsters;

to

[XmlArray("Monsters"),XmlArrayItem("Monster")]
public List<Monster> Monsters = new List<Monster>();

So it will be initialized each time you instanciate a MonsterContainer which makes the “container.Monsters = new” line not needed anymore.

Yes, please add my appreciation to this thread too. One question though, I’ve added a constructor to the javascript “Monster” class like so …

public class Monster {
	@XmlAttribute("name")
	public var Name : String;
	public var Health : int;
	
	public function Monster(tempName : String, tempHealth : int) {
		this.Name = tempName;
		this.Health = tempHealth;
	}
}

… but when I run it, Unity returns with the error …

InvalidOperationException: Monster cannot be serialized because it does not have a default public constructor

But my Constructor is public?

Edit: Nevermind, it looks like the de-serializer needs a parameter free constructor to work.