Saving and Loading Data: XmlSerializer

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Convenience)
(Added syntax highlighting and removed typeof() from Javascript)
Line 4: Line 4:
 
The goal of this tutorial will be to save and load data with the following xml structure.
 
The goal of this tutorial will be to save and load data with the following xml structure.
  
 +
<syntaxhighlight lang="xml">
 
  <MonsterCollection>
 
  <MonsterCollection>
 
  <Monsters>
 
  <Monsters>
Line 14: Line 15:
 
  </Monsters>
 
  </Monsters>
 
  </MonsterCollection>
 
  </MonsterCollection>
 +
</syntaxhighlight>
  
 
== Preparing the Monster class ==
 
== Preparing the Monster class ==
Line 24: Line 26:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  using System.Xml;
 
  using System.Xml;
 
  using System.Xml.Serialization;
 
  using System.Xml.Serialization;
Line 34: Line 37:
 
  public int Health;
 
  public int Health;
 
  }
 
  }
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  import System.Xml;
 
  import System.Xml;
 
  import System.Xml.Serialization;
 
  import System.Xml.Serialization;
Line 45: Line 50:
 
  public var Health : int;
 
  public var Health : int;
 
  }
 
  }
 +
</syntaxhighlight>
 
|}
 
|}
  
Line 64: Line 70:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  using System.Collections.Generic;
 
  using System.Collections.Generic;
 
  using System.Xml;
 
  using System.Xml;
Line 74: Line 81:
 
  public List<Monster> Monsters;
 
  public List<Monster> Monsters;
 
  }
 
  }
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  import System.Collections.Generic;
 
  import System.Collections.Generic;
 
  import System.Xml;
 
  import System.Xml;
Line 85: Line 94:
 
  public var Monsters : List.<Monster>;
 
  public var Monsters : List.<Monster>;
 
  }
 
  }
 +
</syntaxhighlight>
 
|}
 
|}
 
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.
 
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.
Line 97: Line 107:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  var serializer = new XmlSerializer(typeof(MonsterContainer));
 
  var serializer = new XmlSerializer(typeof(MonsterContainer));
 
  var stream = new FileStream(path, FileMode.Open);
 
  var stream = new FileStream(path, FileMode.Open);
 
  var container = serializer.Deserialize(stream) as MonsterContainer;
 
  var container = serializer.Deserialize(stream) as MonsterContainer;
 
  stream.Close();
 
  stream.Close();
 +
</syntaxhighlight>
 
|
 
|
  var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
+
<syntaxhighlight lang="javascript">
 +
  var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 
  var stream : Stream = new FileStream(path, FileMode.Open);
 
  var stream : Stream = new FileStream(path, FileMode.Open);
 
  var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
 
  var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
 
  stream.Close();
 
  stream.Close();
 +
</syntaxhighlight>
 
|}
 
|}
  
Line 114: Line 128:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  var serializer = new XmlSerializer(typeof(MonsterContainer));
 
  var serializer = new XmlSerializer(typeof(MonsterContainer));
 
  var stream = new FileStream(path, FileMode.Create));
 
  var stream = new FileStream(path, FileMode.Create));
 
  serializer.Serialize(stream, this);
 
  serializer.Serialize(stream, this);
 
  stream.Close();
 
  stream.Close();
 +
</syntaxhighlight>
 
|
 
|
  var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
+
<syntaxhighlight lang="javascript">
 +
  var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 
  var stream : Stream = new FileStream(path, FileMode.Create);
 
  var stream : Stream = new FileStream(path, FileMode.Create);
 
  serializer.Serialize(stream, this);
 
  serializer.Serialize(stream, this);
 
  stream.Close();
 
  stream.Close();
 +
</syntaxhighlight>
 
|}
 
|}
 
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.
 
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.
Line 136: Line 154:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  using System.Collections.Generic;
 
  using System.Collections.Generic;
 
  using System.Xml;
 
  using System.Xml;
Line 172: Line 191:
 
  }
 
  }
 
  }
 
  }
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  import System.Collections.Generic;
 
  import System.Collections.Generic;
 
  import System.Xml;
 
  import System.Xml;
Line 186: Line 207:
 
  public function Save(path : String)
 
  public function Save(path : String)
 
  {
 
  {
  var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
+
  var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 
  var stream : Stream = new FileStream(path, FileMode.Create);
 
  var stream : Stream = new FileStream(path, FileMode.Create);
 
  serializer.Serialize(stream, this);
 
  serializer.Serialize(stream, this);
Line 194: Line 215:
 
  public static function Load(path : String):MonsterContainer  
 
  public static function Load(path : String):MonsterContainer  
 
  {
 
  {
  var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
+
  var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 
  var stream : Stream = new FileStream(path, FileMode.Open);
 
  var stream : Stream = new FileStream(path, FileMode.Open);
 
  var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
 
  var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
Line 204: Line 225:
 
         public static function LoadFromText(text : String):MonsterContainer
 
         public static function LoadFromText(text : String):MonsterContainer
 
         {
 
         {
  var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
+
  var serializer : XmlSerializer = new XmlSerializer(MonsterContainer);
 
  return serializer.Deserialize(new StringReader(text)) as MonsterContainer;
 
  return serializer.Deserialize(new StringReader(text)) as MonsterContainer;
 
  }
 
  }
 
  }
 
  }
 +
</syntaxhighlight>
 
|}
 
|}
  
Line 218: Line 240:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 
  var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 
  var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
 +
</syntaxhighlight>
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  var xmlData = @"<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></MonsterCollection>";
 
  var xmlData = @"<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></MonsterCollection>";
 
  var monsterCollection = MonsterContainer.LoadFromText(xmlData);  
 
  var monsterCollection = MonsterContainer.LoadFromText(xmlData);  
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  var xmlData : String = '<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></MonsterCollection>';
 
  var xmlData : String = '<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></MonsterCollection>';
 
  var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(xmlData);
 
  var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(xmlData);
 +
</syntaxhighlight>
 
|}
 
|}
  
Line 236: Line 266:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
 
  monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
 
  monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
 +
</syntaxhighlight>
 
|}
 
|}
  
Line 263: Line 297:
 
|+
 
|+
 
|
 
|
 +
<syntaxhighlight lang="csharp">
 
  IEnumerator Start()
 
  IEnumerator Start()
 
  {
 
  {
Line 269: Line 304:
 
     var monsterCollection = MonsterContainer.LoadFromText(www.text);
 
     var monsterCollection = MonsterContainer.LoadFromText(www.text);
 
  }
 
  }
 +
</syntaxhighlight>
 
|
 
|
 +
<syntaxhighlight lang="javascript">
 
  function Start()
 
  function Start()
 
  {     
 
  {     
Line 276: Line 313:
 
     var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(www.text);
 
     var monsterCollection : MonsterCollection = MonsterContainer.LoadFromText(www.text);
 
  }
 
  }
 +
</syntaxhighlight>
 
|}
 
|}
  

Revision as of 09:36, 25 January 2012

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
Monster.cs Monster.js
 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;
 }

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
MonsterContainer.cs MonsterContainer.js
 using System.Collections.Generic;
 using System.Xml;
 
 [XmlRoot("MonsterCollection")]
 public class MonsterContainer
 {
 	[XmlArray("Monsters")]
 	[XmlArrayItem("Monster")]
 	public List<Monster> Monsters;
 }
 import System.Collections.Generic;
 import System.Xml;
 
 @XmlRoot("MonsterCollection")
 public class MonsterContainer
 {
 	@XmlArray("Monsters")
  	@XmlArrayItem("Monster")
 	public var Monsters : 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

C# Javascript/Unityscript
 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();

Writing data

C# Javascript/Unityscript
 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();

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></MonsterCollection>";
 var monsterCollection = MonsterContainer.LoadFromText(xmlData);
 var xmlData : String = '<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></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.

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);
 }

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