Saving and Loading Data: XmlSerializer

From Unify Community Wiki
Revision as of 15:54, 14 November 2013 by Marcus Mattsson (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

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

C# Javascript/Unityscript Boo
Monster.cs Monster.js Monster.boo
 using System.Xml;
 using System.Xml.Serialization;
 
 public class Monster
 { 
 	[XmlAttribute("name")]
 	public string Name;
 
 	public int Health;
 }
 import System.Xml;
 import System.Xml.Serialization;
 
 public class Monster
 {
 	@XmlAttribute("name")
 	public var Name : String;
 
 	public var Health : int;
 }
import System.Xml.Serialization
 
public class Monster: 
	[XmlAttribute("name")] 
	public Name as string
	public Health as 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. <Health>5</Health>). If you want it to be parsed as attribute ( e.g. <Monster name="a"> ) 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.

C# Javascript/Unityscript Boo
MonsterContainer.cs MonsterContainer.js MonsterContainer.boo
 using System.Collections.Generic;
 using System.Xml.Serialization;
 
 [XmlRoot("MonsterCollection")]
 public class MonsterContainer
 {
 	[XmlArray("Monsters")]
 	[XmlArrayItem("Monster")]
 	public List<Monster> Monsters = new List<Monster>();
 }
 import System.Collections.Generic;
 import System.Xml.Serialization;
 
 @XmlRoot("MonsterCollection")
 public class MonsterContainer
 {
 	@XmlArray("Monsters")
  	@XmlArrayItem("Monster")
 	public var Monsters : List.<Monster> = new List.<Monster>();
 }
import System.Collections.Generic
import System.Xml.Serialization
 
[XmlRoot("MonsterCollection")]
public class MonsterContainer: 
	[XmlArray("Monsters")]
	[XmlArrayItem("Monster")]
	public Monsters as List[of Monster] = List[of 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

C# Javascript/Unityscript Boo
 var serializer = new XmlSerializer(typeof(MonsterContainer));
 var stream = new FileStream(path, FileMode.Open);
 var container = serializer.Deserialize(stream) as MonsterContainer;
 stream.Close();
 var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 var stream : Stream = new FileStream(path, FileMode.Open);
 var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
 stream.Close();
serializer = XmlSerializer(typeof(MonsterContainer))
stream = FileStream(path, FileMode.Open)
container = serializer.Deserialize(stream) as MonsterContainer

Writing data

C# Javascript/Unityscript Boo
 var serializer = new XmlSerializer(typeof(MonsterContainer));
 var stream = new FileStream(path, FileMode.Create));
 serializer.Serialize(stream, this);
 stream.Close();
 var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 var stream : Stream = new FileStream(path, FileMode.Create);
 serializer.Serialize(stream, this);
 stream.Close();
serializer = XmlSerializer(typeof(MonsterContainer))
stream = 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.

C# Javascript/Unityscript
MonsterContainer.cs MonsterContainer.js
 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;
 		}
 	}
 
         //Loads the xml directly from the given string. Useful in combination with www.text.
         public static MonsterContainer LoadFromText(string text) 
 	{
 		var serializer = new XmlSerializer(typeof(MonsterContainer));
 		return serializer.Deserialize(new StringReader(text)) as MonsterContainer;
 	}
 }
 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(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(MonsterContainer);
 		var stream : Stream = new FileStream(path, FileMode.Open);
 		var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
 		stream.Close();
 		return result;
 	}
 
         //Loads the xml directly from the given string. Useful in combination with www.text.
         public static function LoadFromText(text : String):MonsterContainer
         {
 		var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 		return serializer.Deserialize(new StringReader(text)) as MonsterContainer;
 	}
 }

Usage

Reading

C# Javascript/Unityscript
 var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 var xmlData = @"<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></Monsters></MonsterCollection>";
 var monsterCollection = MonsterContainer.LoadFromText(xmlData);
 var xmlData : String = '<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></Monsters></MonsterCollection>';
 var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(xmlData);

Writing

C# Javascript/Unityscript
 monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
 monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));

Notes

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, properties and class you want to be serialized have to be public. private, protected or internal doesn't work
  • If you want to serialize a property both a getter and a setter have to be present.

Deployment

For this code to work after being deployed you need to take additional care.

Standalone Player

After you deployed your application your Application.dataPath will point to "(PathToExecutable)/executable_Data". So you need to take care to store your monsters.xml there for the application to find it.

Webplayer

The webplayer can't access files on any computer directly so using the methods described before to read/write data won't work here. To load any data you need to use the WWW class.

Example that loads the monsters.xml file directly from where your webplayer html is located.

C# Javascript/Unityscript
 IEnumerator Start()
 {
    var www = new WWW(Path.Combine(Application.dataPath, "monsters.xml"));
    yield return www;
    var monsterCollection = MonsterContainer.LoadFromText(www.text);
 }
 function Start()
 {    
    var www : WWW = new WWW(Path.Combine(Application.dataPath, "monsters.xml"));
    yield return www;
    var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(www.text);
 }

iOS Devices

A NullReferenceException will be raised if you're using List<T> in your MonsterContainer class, use an array instead. Similarly the same exception is raised if you're using get and set to implement properties in the class you're trying to serialize.

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 here [1]
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox