GazTheDestroyer
GazTheDestroyer

Reputation: 21261

Setter not called when deserializing collection

I am trying to do a very simple bit of serialization with XmlSerializer:

public struct XmlPerson
{
    [XmlAttribute] public string Id   { get; set; }
    [XmlAttribute] public string Name { get; set; }
}

public class GroupOfPeople
{
    private Dictionary<string, string> _namesById = new Dictionary<string, string>();

    //pseudo property for serialising dictionary to/from XML
    public List<XmlPerson> _XmlPeople
    {
        get
        {
            var people = new List<XmlPerson>();
            foreach (KeyValuePair<string, string> pair in _namesById )
                people.Add(new XmlPerson() { Id = pair.Key, Name = pair.Value });

            return people;
        }
        set
        {
            _namesById.Clear();
            foreach (var person in value)
                _namesById.Add(person.Id, person.Name);
        }
    }     
} 

Saving this class works fine, and I get:

<?xml version="1.0" encoding="utf-8"?>
<GroupOfPeople xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <_XmlPeople>
        <XmlPerson Id="person1" Name="Fred" />
        <XmlPerson Id="person2" Name="Bill" />
        <XmlPerson Id="person3" Name="Andy" />
        <XmlPerson Id="person4" Name="Nagesh" />
    </_XmlPeople>
</GroupOfPeople>

However, when I read in the file again, my _XmlPeople property setter is never called, and thus the dictionary is empty. All other properties on this object get deserialized fine.

Am I missing something obvious? I have tried various collection types but none of them deserialize.

EDIT: Read code:

try
{
    using (var stream = new StreamReader(itemPath))
    {
        var xml = new XmlSerializer(typeof(GroupOfPeople));
        GroupOfPeople item = (GroupOfPeople)xml.Deserialize(stream);  
    }  
}
//snip error stuff

Upvotes: 7

Views: 4045

Answers (3)

Florian Winter
Florian Winter

Reputation: 5289

In this question, the _XmlPeople property serves as a "proxy" for the _namesById dictionary, which is where the collection elements are actually stored. Its getters and setters convert between different types of collections.

This does not work with deserialization, and the reason is pointed out in https://stackoverflow.com/a/10283576/2279059. (Deserialization calls the getter, then adds elements to the "temporary" collection it returns, which is then discarded).

A generic solution is to implement the "proxy collection" as a separate class, then have a property which is a proxy collection:

(In the code below, the "actual collection" is _nameById, and MyItem is XmlPerson)

public class MyProxyCollection : IList<MyItem> {
  MyProxyCollection(... /* reference to actual collection */ ...) {...}
  // Implement IList here
}

public class MyModel {
  MyProxyCollection _proxy;

  public MyModel() {
    _proxy = new MyProxyCollection (... /* reference to actual collection */ ...);
  }

  // Here we make sure the getter and setter always return a reference to the same
  // collection object. This ensures that we add items to the correct collection on
  // deserialization.
  public MyProxyCollection Items {get; set;}
}

This ensures that getting/setting the collection object works as expected.

Upvotes: 0

GazTheDestroyer
GazTheDestroyer

Reputation: 21261

Answer for clarity:

Have done some debugging and found that XmlSerializer does not call the setter for a collection.

Instead is calls the getter, and then adds items to the collection returned. Thus a solution such as Felipe's is necessary.

Upvotes: 19

Smur
Smur

Reputation: 3113

Have you tried using the XmlArray attribute?

With your example it would be something like this:

[XmlArray]
[XmlArrayItem(ElementName="XmlPerson")]
public List<XmlPerson> XmlPeople

EDIT:

Here, try the following structure:

public struct XmlPerson
{
    [XmlAttribute] public string Id   { get; set; }
    [XmlAttribute] public string Name { get; set; }
}


public class GroupOfPeople
{
    [XmlArray]
    [XmlArrayItem(ElementName="XmlPerson")]
    public List<XmlPerson> XmlPeople { get; set; }
}

I don't think it will be easy to add code to the Setter of the list, so what about getting that Dictionary when you actually need it?

Like this:

private Dictionary<string, string> _namesById;

public Dictionary<string, string> NamesById
{
    set { _namesById = value; }
    get
    {
        if (_namesById == null)
        {
            _namesById = new Dictionary<string, string>();

            foreach (var person in XmlPeople)
            {
                 _namesById.Add(person.Id, person.Name);
            }
        }

        return _namesById;
    }
}

This way you'll get the items from the XML and will also mantain that Dictionary of yours.

Upvotes: 2

Related Questions