Reputation: 21261
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
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
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
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