简体   繁体   中英

XmlSerializer.Deserialize() isn't deserializing a List<T> correctly

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?

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

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.

I know this is old but i have the same problem.

What i found is that if i have an object that implements IXmlSerializable in the graph (so the main object that i want to deserialize dosen't implement the interface, but an object in it implements it), it ruins all the deserialization. Lists don't deserialize anymore, neither the object i'm talking about. Serialization works perfect.

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 ) )]

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.

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.

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.

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM