views:

2523

answers:

7

I am new to the C# XmlSerializer so I might be missing something basic here.

The problem I am running into is that I have one class that has a List<T> of another class. When I serialize the main class the XML looks beautiful and all the data is intact. When I deserialize the XML, the data in the List<T> disappears and I am left with an empty List<T>. I am not receiving any errors and the serialization portion works like charm.

What am I missing with the deserialization process?

EDIT: Note that the code shown below does not reproduce the problem - it works. This was a simplified version of the real code, which did not work. Unfortunately, the code below was simplified enough to not reproduce the problem!

public class User
{
  public User()
  {
    this.Characters = new List<Character>();
  }
  public string Username { get; set; }
  public List<Character> Characters { get; set; }
}

public class Character
{
  public Character()
  {
    this.Skills = new List<Skill>();
  }
  public string Name { get; set; }
  public List<Skill> Skills { get; set; }
}

public enum Skill
{
  TreeClimber,
  ForkliftOperator
}

public static void Save(User user)
{
    using (var textWriter = new StreamWriter("data.xml"))
    {
        var xmlSerializer = new XmlSerializer(typeof(User));
        xmlSerializer.Serialize(textWriter, user);
    }
}

public static User Restore()
{
    if (!File.Exists("data.xml"))
        throw new FileNotFoundException("data.xml");

    using (var textReader = new StreamReader("data.xml"))
    {
        var xmlSerializer = new XmlSerializer(typeof(User));
        return (User)xmlSerializer.Deserialize(textReader);
    }
}

public void CreateAndSave()
{
  var character = new Character();
  character.Name = "Tranzor Z";
  character.Skills.Add(Skill.TreeClimber);

  var user = new User();
  user.Username = "Somebody";
  user.Characters.Add(character);

  Save(user);
}

public void RestoreAndPrint()
{
  var user = Restore();
  Console.WriteLine("Username: {0}", user.Username);
  Console.WriteLine("Characters: {0}", user.Characters.Count);
}

The XML generated by executing CreateAndSave() looks like so:

<User>
  <Username>Somebody</Username>
  <Characters>
    <Character>
      <Name>Tranzor Z</Name>
      <Skills>
        <Skill>TreeClimber</Skill>
      </Skills>
    </Character>
  <Characters>
</User>

Perfect! That's the way it should look. If I then execute RestoreAndPrint() I get a User object with the Username property set properly but the Characters property is an empty list:

Username: Somebody
Characters: 0

Can anybody explain to me why the Characters property is serialized properly but won't deserialize?

A: 

It might have to do with the deserializing of the enum....since it serialized it as the string value...might have trouble creating the correct enum value for the character. Have not tested this hypothesis...

CSharpAtl
In my actual code project the root class does have other enums that are being serialized and deserialized properly. I can serialize and deserialize the individual objects within the Characters list successfully too. It is only when deserializing a User object that the process breaks down and the Characters list is empty and no exceptions are being thrown.
Andy
-1: there is no problem with the enum. I'd recommend in future either test, or say nothing.
John Saunders
A: 
Stream stream = File.Open(filename + ".xml", FileMode.Open);
User user = null;
using (XmlReader reader = XmlReader.Create(stream))
{
    user = IntermediateSerializer.Deserialize<User>(reader, null);
}
stream.Close();

return user;

I would try using something like this.

Chris Watts
The code I am working with *is* game related but I am not referencing the XNA Framework in this project so I have no access to the IntermediateSerializer.
Andy
Sorry, i made the assumption.
Chris Watts
+5  A: 

Cannot reproduce; I get (after fixing the more immediate bugs):

Username: Somebody
Characters: 1

Changes:

  • WriteLine instead of WriteFormat (which prevented it compiling)
  • init the lists in the default constructors (which prevented CreateAndSave from working):
    • public User() { Characters = new List<Character>(); }
    • public Character() { Skills = new List<Skill>(); }
Marc Gravell
Hrm... The code I posted above was sample code that mimicked my real code, hence my mistakes, sorry 'bout that. My actual project is significantly larger but the code posted is a pretty close approximation as to what I am trying to do.My intent was to provide an example that represents my situation without providing all the gory details. :/
Andy
Unfortunately, I think the problem is in the code that is omitted.
Marc Gravell
Tongue in cheek plug: you wouldn't have this problem with (the free) protobuf-net ;-p
Marc Gravell
Heh. I'll take a look a protobuf-net but my goal for this project was a human readable/editable format that could be manipulated with any basic text editor.
Andy
+1  A: 

In the past, when serializing lists, I've used the [XmlArray] and [XmlArrayItem] annotations. You would then put an [XmlIgnore] annotation on the Characters property. In your case, it would look something like this:

[XmlArray("Characters")]
[XmlArrayItem("Character", Type=typeof(Character))]
public Character[] _ Characters
{
    get
    {
        //Make an array of Characters to return 
        return Characters.ToArray();
    }

    set
    {
        Characters.Clear();
        for( int i = 0; i < value.Length; i++ )
            Characters.Add( value[i] );
    }
}

Hope that helps.

Dov
No luck -- I am getting the same result, i.e. an empty list. When I deserialize, none of the Character-related code is being touched. It's as if the entire Characters node in the XML is being thrown away without being processed.
Andy
A: 

Perhaps, instead of a StreamReader, create an XmlReader, instead. Also, as a troubleshooting step, you might try your existing code with explicit types (as below) instead of "var" declarations.

public static User Restore()
{
  if (!File.Exists("data.xml"))
    throw new FileNotFoundException("data.xml");

  XmlReader xr = XmlReader.Create("data.xml");
  XmlSerializer serializer = new XmlSerializer(typeof(User));
  var user = (User)serializer.Deserialize(xr);
  xr.Close();
  return user;
}

EDIT: Also, try the XmlInclude annotation on your User class.

[XmlInclude( typeof( Character ) )]
Dov
I added XmlIncludeAttributes to all relevant classes and the deserialization still failed.
Andy
A: 

After futzing around with the code for a long time I finally gave up on the idea of using the default behaviour of the XmlSerializer.

All of the relevant classes now inherit IXmlSerializer and implement the ReadXml() and WriteXml() functions. I was really hoping to take the lazy route but now that I've played with IXmlSerializer I realize that it really isn't difficult at all and the added flexibility is really nice.

Andy
A: 

I added this attribute to my List types and it worked then (had your same problem):

[System.Xml.Serialization.XmlElementAttribute("NameOfMyField")]
public List<SomeType> NameOfMyField{get;set;}

I think this is your solution.

boomhauer