Is there a way to deserialize a JSON array directly into the property of an object?
I have a JSON consisting of an array of Entry objects. To get them into a Collection class I could go the route of:
var coll = new Collection();
coll.Entries = JsonConvert.DeserializeObject<List<Entry>>(json);
Is there a way to define Collection in a way to skip this step an directly call var coll = JsonConvert.DeserializeObject<Collection>(json)
You could let your Collection class implement IEnumerable<Entry> and decorate it with the JsonObject attribute. This should give Json.NET the ability to deserialize your class correctly.
[JsonObject]
public class Collection : IEnumerable<Entry>
{
public IEnumerable<Entry> Entries { get; };
public GetEnumerator() {
return Entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
While silkfires answer didn't work for me, it gave me a hint for the solution:
I added the [JsonArray] attribute and implemented ICollection<Entry> by pointing all methods to the Entries list.
[JsonArray]
public class Collection: ICollection<Entry>
{
public LinkedList<Entry> Entries { get; } = new LinkedList<Entry>();
public void Add(Entry item)
{
Entries.AddLast(item);
}
...
Related
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
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.
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.
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.
I have some class with tree node structure. It has Children property with read only collection type for hide direct changing of children and AddChild(...) method for control children adding.
class TreeNode {
List<TreeNode> _children = new List<TreeNode>();
public IReadOnlyList<TreeNode> Children => children;
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node){
// ... some code
_children.Add(node);
}
}
And I need to provide deserialization for my class. I tried:
[Serializable]
[XmlRoot(ElementName = "node")]
class TreeNode {
List<TreeNode> _children = new List<TreeNode>();
[XmlElement(ElementName = "node")]
public IReadOnlyList<TreeNode> Children => children;
[XmlAttribute(DataType = "string", AttributeName = "name")]
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node){
// ... some code
_children.Add(node);
}
public static TreeNode Deserialize(Stream stream) {
var serializer = new XmlSerializer(typeof(TreeNode));
var obj = serializer.Deserialize(stream);
var tree = (TreeNode)obj;
return tree;
}
}
Of course, this doesn't work because IReadOnlyList has no Add method.
Is it possible to bind AddChild to deserialization process? And if 'yes' - How?
How to provide the same encapsulation level with deserialization ability?
If this is XmlSerializer, then: no, you can't do that unless you implement IXmlSerializable completely, which is very hard to do correctly, and defeats the entire purpose of using XmlSerializer in the first place.
If the data isn't huge, then my default answer to any problem of the form "my existing object model doesn't work well with my chosen serializer" is: when it gets messy, stop serializing your existing object model. Instead, create a separate DTO model that is designed solely to work well with your chosen serializer, and map the data into the DTO model before serialization - and back again afterwards. This might mean using List<T> in the DTO model rather than IReadOnlyList<T>.
This can be done by adding a surrogate property to TreeNode that returns a surrogate wrapper type that implements both IEnumerable<T> and Add(T) using delegates provided to its constructor. First, introduce the following surrogate wrapper:
// Proxy class for any enumerable with the requisite `Add` methods.
public class EnumerableProxy<T> : IEnumerable<T>
{
readonly Action<T> add;
readonly Func<IEnumerable<T>> getEnumerable;
// XmlSerializer required default constructor (which can be private).
EnumerableProxy()
{
throw new NotImplementedException("The parameterless constructor should never be called directly");
}
public EnumerableProxy(Func<IEnumerable<T>> getEnumerable, Action<T> add)
{
if (getEnumerable == null || add == null)
throw new ArgumentNullException();
this.getEnumerable = getEnumerable;
this.add = add;
}
public void Add(T obj)
{
// Required Add() method as documented here:
// https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.100%29.aspx
add(obj);
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return (getEnumerable() ?? Enumerable.Empty<T>()).GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
Next, modify your TreeNode by marking Children with [XmlIgnore] and adding a surrogate property that returns a pre-allocated EnumerableProxy<TreeNode>:
[XmlRoot(ElementName = "node")]
public class TreeNode
{
List<TreeNode> _children = new List<TreeNode>();
[XmlIgnore]
public IReadOnlyList<TreeNode> Children { get { return _children; } }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
[XmlElement(ElementName = "node")]
public EnumerableProxy<TreeNode> ChildrenSurrogate
{
get
{
return new EnumerableProxy<TreeNode>(() => _children, n => AddChild(n));
}
}
[XmlAttribute(DataType = "string", AttributeName = "name")]
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node)
{
// ... some code
_children.Add(node);
}
}
Your type can now be fully serialized and deserialized by XmlSerializer. Working .NET fiddle.
This solution takes advantage of the following documented behaviors of XmlSerializer. Firstly, as stated in Remarks for XmlSerializer:
The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases.
Thus your surrogate IEnumerable<T> wrapper does not actually need to implement ICollection<T> with its full set of methods including Clear(), Remove(), Contains() and so on. Just an Add() with the correct signature is sufficient. (If you wanted to implement a similar solution for, say, Json.NET, your surrogate type would need to implement ICollection<T> - but you could just throw exceptions from the unnecessary methods such as Remove() and Clear().)
Secondly, as stated in Introducing XML Serialization:
XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections).
I.e. XmlSerializer can successfully deserialize the items in a pre-allocated collection even when that collection is returned by a get-only property. This avoids the need to implement a set method for the surrogate property or a default constructor for the surrogate collection wrapper type.
I am trying to get my observablelist which is derived from observablecollection to be serialized. For some reason the collection has always 0 Elements when I deserialize it. When I change the collectiontype from observablelist to observablecollection in class "Test" it works fine. So, how can I achive that my class is also handled like a normal list. Hope anyone can help me. Here is my Code:
[Serializable]
[ProtoContract]
public class ObservableList<T> : ObservableCollection<T>
{
...
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Test
{
public string Name { get; set; }
public ObservableList<Hobby> Hobbies { get; set; } = new ObservableList<Hobby>();
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Hobby
{
public string Name { get; set; }
}
KR Manuel
If I use this Class it's not working but, if i rename the Add() function to AddRange() for example it is. Can anyone tell me the reason for this strange behaviour?
public class ObservableList<T> : ObservableCollection<T>
{
public void Add(IEnumerable<T> list)
{
foreach (var item in list)
Add(item);
}
}
protobuf-net needs to support arbitrary lists / collections - not just those that derive from List<T> or support specific interfaces. It does this by attempting to resolve a suitable GetEnumerator() method (for serialization) and Add() method (for deserialization). There are more than a few checks and priorities built into this, but it sounds like in this specific case it is getting confused as to your intent.
It seems to work fine here... using your code, and:
static void Main()
{
var test = new Test
{
Hobbies =
{
new Hobby { Name = "abc" }
}
};
var clone = Serializer.DeepClone(test);
Console.WriteLine("Same object? {0}",
ReferenceEquals(test, clone));
Console.WriteLine("Sub items: {0}",
clone.Hobbies.Count);
foreach (var x in clone.Hobbies)
{
Console.WriteLine(x.Name);
}
}
gives the output:
Same object? False
Sub items: 1
abc
So: the deserialized object has the correct sub-item.
I found out whats going on. Here again my Observablelist Class:
[Serializable]
[ProtoContract]
public class ObservableList<T> : ObservableCollection<T>
{
public void Add(IEnumerable<T> list)
{
foreach (var item in list)
Add(item);
}
}
If I use this Class it's not working but, if i rename the Add() function to AddRange() for example it is. Can anyone tell me the reason for this strange behaviour?
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
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.
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.
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.
When I try to serialize this collection, the name property is not serialized.
public class BCollection<T> : List<T> where T : B_Button
{
public string Name { get; set; }
}
BCollection<BB_Button> bc = new BCollection<B_Button>();
bc.Name = "Name";// Not Serialized!
bc.Add(new BB_Button { ID = "id1", Text = "sometext" });
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(bc);
Only if I create a new class (without List<t> inheritance), and define there string Name property and List<B_Button> bc = new List<B_Button>(); property I get the right result.
In many serializers (and data-binding, in fact), an object is either an entity or (exclusive) a list; having properties on a list is not commonly supported. I would refactor to encapsulate the list:
public class Foo<T> {
public string Name {get;set;}
private readonly List<T> items = new List<T>();
public List<T> Items { get { return items; } }
}
Also; how would you plan on representing that in JSON? IIRC the JSON array syntax doesn't allow for extra properties either.