I have a XML file:
<Hand cards="C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4">
</Hand>
I define a class
[Serializable()]
[XmlRoot("Hand")]
public class Hand
{
[XmlAttribute("cards")]
public List<string> Cards{get;set;}
}
How to deserialize a XML to object in this case? Hand object result must have Cards = {C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4}.
You cannot.
What you can do is to create a property which will do this conversion in its getter/setter
[XmlIgnore]
public List<string> CardList { get; private set; }
[XmlAttribute("cards")]
public string Cards {
get { return String.Join(",", CardList); }
set { CardList = value.Split(",").ToList(); }
}
You can do this with the help of IXmlSerializable. Read more about it on MSDN.
This way
[Serializable()]
[XmlRoot("Hand")]
public class Hand : IXmlSerializable {
[XmlAttribute("cards")]
public List<string> Cards { get; set; }
public System.Xml.Schema.XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) {
this.Cards = new List<string>(reader.GetAttribute("cards").Split(','));
}
public void WriteXml(XmlWriter writer) {
writer.WriteAttributeString("cards",
string.Join(",",
this.Cards != null ? this.Cards : new List<string>()));
}
}
Hope this helps you.
Related
How to serialize the OrganizationType class?
No additional properties - wrappers.
public class INNType : IConvertToString
{
protected INNType() { }
public string Value { get; }
public INNType(string inn)
{
if (!Regex.IsMatch(inn, #"^\d{10}$")) throw new Exception(#"");
Value = inn;
}
public override string ToString() => Value;
public static implicit operator INNType(string inn) => new INNType(inn);
public static implicit operator string(INNType inn) => inn?.Value;
}
[Serializable]
[XmlType(Namespace = "http://roskazna.ru/gisgmp/xsd/Organization/2.2.0")]
public class OrganizationType
{
protected OrganizationType() {}
[XmlAttribute("inn")]
public INNType Inn {get; set;}
}
After serialization, it should look like below.
<OrganizationType inn="1234567890" />
The method used to serialize objects looks like this
public static XmlDocument SerializerObject<T>(T obj, XmlSerializerNamespaces xsn) where T : class
{
XmlDocument xmlDocument = new XmlDocument();
using (var xs = xmlDocument.CreateNavigator().AppendChild())
{
new XmlSerializer(typeof(T)).Serialize(xs, obj, xsn);
}
return xmlDocument;
}
Exception
System.InvalidOperationException : Cannot serialize member 'Inn' of type GisGmp.INNType. XmlAttribute/XmlText cannot be used to encode complex types.
I have to do it. Not good.
[XmlIgnore]
public INNType Inn {get; set;}
[XmlAttribute("inn")]
public string WrapperInn { get => Inn; set => Inn = value; }
You could implement IXmlSerializable:
[Serializable]
public class OrganizationType : IXmlSerializable
{
public OrganizationType()
{
// Demo
this.Inn = new INNType("0123456789");
}
[XmlAttribute("inn")]
public INNType Inn { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
return;
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("inn", this.Inn.Value);
}
}
Output:
<OrganizationType inn="0123456789" />
I have a weird XML setup here: I need to interface with a third-party and their XML layout that they're using is beyond my control - no chance to changing anything...
In the scope of a larger XML, I find myself needing to serialize (and later also deserialize) a list of items (for a mobile device) which are defined as having a Type and a Number property (both strings). So this would be something like:
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
And this would normally serialize a List<SerialNumber> SerialNumbers as
<SerialNumbers>
<SerialNumber>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
</SerialNumber>
<SerialNumber>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumber>
</SerialNumbers>
However, in my case, I need to send this XML:
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
So basically, the list elements need to be omitted - I just need a container <SerialNumbers> and then for each entry in the list, I only need to serialize out the Type and Number subelements.
How can I do this easily in .NET with the XmlSerializer ?
I tried to use
[XmlRoot(ElementName="")]
public class SerialNumber
or
[XmlArray]
[XmlArrayItem(ElementName = "")]
public List<SerialNumber> SerialNumbers { get; set; }
but neither of these worked - I still get my full serialization with the <SerialNumber> elements inside the <SerialNumbers> container...
Is there an easy trick to achieve what I'm looking for? I'd much rather not go low-level and start concetanating together my XML manually....
Thanks!
You could use custom serialization with IXmlSerializable.
public class SerialNumbers : List<SerialNumber>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Clear();
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
var serialNumber = new SerialNumber
{
Type = reader.ReadElementContentAsString("Type", ""),
Number = reader.ReadElementContentAsString("Number", "")
};
Add(serialNumber);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
foreach (var serialNumber in this)
{
writer.WriteElementString("Type", serialNumber.Type);
writer.WriteElementString("Number", serialNumber.Number);
}
}
}
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Example:
var ser = new XmlSerializer(typeof(SerialNumbers));
var reader = new StringReader(#"
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
".Trim());
var result = (SerialNumbers) ser.Deserialize(reader);
var writer = new StringWriter();
ser.Serialize(writer, result);
Result:
<?xml version="1.0" encoding="utf-16"?>
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
This might do the trick for you
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class SerialNumbers
{
private string[] itemsField;
private ItemsChoiceType[] itemsElementNameField;
[System.Xml.Serialization.XmlElementAttribute("Number", typeof(string))]
[System.Xml.Serialization.XmlElementAttribute("Type", typeof(string))]
[System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")]
public string[] Items
{
get
{
return this.itemsField;
}
set
{
this.itemsField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute("ItemsElementName")]
[System.Xml.Serialization.XmlIgnoreAttribute()]
public ItemsChoiceType[] ItemsElementName
{
get
{
return this.itemsElementNameField;
}
set
{
this.itemsElementNameField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(IncludeInSchema = false)]
public enum ItemsChoiceType
{
Number,
Type,
}
I have a class that inherits from List<T> and add one more class property
public class DrivenList : List<int>
{
public string Name {get;set;}
private DrivenList() { }
public DrivenList(string name) { this.Nname = name; }
}
When JSON serializing the object using Newtonsoft.Json, I get only the list item. ( [1,2,3] )
Any ideas how to add the Name property to the results?
Solved: by adding this attribute to the list
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class DrivenList : List<int>
As far as I know with Newtonsoft all you can do is something like this:
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class DrivenList : List<int>
{
[JsonProperty]
public string Name { get; set; }
private DrivenList() { }
public DrivenList(string name) { this.Name = name; }
}
But this will add you unwanted (maybe) fields.
Personally I will do composition instead of inheritance:
public class DrivenList
{
public string Name { get; set; }
public List<int> Items { get; set; }
private DrivenList() { }
public DrivenList(string name) { this.Name = name; }
}
What .NET version are you using and what serializer?
If you are using the standard serializer, then adding [DataMember] annotations would be the answer.
https://msdn.microsoft.com/en-us/library/bb412179(v=vs.110).aspx
But I would suggest to use Json.NET http://www.newtonsoft.com/json
Update:
I would suggest not to directly inherit to List
class Program
{
static void Main(string[] args)
{
var list = new Driven("Ragnarok");
list.Items.Add(1);
list.Items.Add(2);
string json = JsonConvert.SerializeObject(list);
Console.WriteLine(json);
Console.ReadKey();
}
}
public class Driven
{
public Driven(string name)
{
this.Name = name;
}
public List<int> Items { get; set; } = new List<int>();
public string Name { get; set; }
}
Output:
{"Items":[1,2],"Name":"Ragnarok"}
An alternative solution could be to delegate the implementation of IList to your own property. Then you can use the DataContractSerializer. The upside of this is that all the existing C#-code (and all your tests) will still be intact, and no custom logic is needed for serializing it.
If that's not an option and you really want to use NewtonSoft, then you should take a look at this answer, and create your own JsonConverter.
How to serialize/deserialize a custom collection with additional properties using Json.Net
The problem is the following: when an object implements IEnumerable,
JSON.net identifies it as an array of values and serializes it
following the array Json syntax (that does not include properties)
Here's an example that use DataContractSerializer:
JsonHelper from CodeProject
public class JsonHelper
{
/// <summary>
/// JSON Serialization
/// </summary>
public static string JsonSerializer<T>(T t)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, t);
string jsonString = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
return jsonString;
}
/// <summary>
/// JSON Deserialization
/// </summary>
public static T JsonDeserialize<T>(string jsonString)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj = (T)ser.ReadObject(ms);
return obj;
}
}
And the new implementation of your class.
[DataContract]
public class DrivenList : IList<int>
{
[DataMember]
public List<int> Items = new List<int>();
[DataMember]
public string Name { get; set; }
private DrivenList() { }
public DrivenList(string name) { this.Name = name; }
#region Implementation of IList
public IEnumerator<int> GetEnumerator()
{
return Items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Items).GetEnumerator();
}
public void Add(int item)
{
Items.Add(item);
}
public void Clear()
{
Items.Clear();
}
public bool Contains(int item)
{
return Items.Contains(item);
}
public void CopyTo(int[] array, int arrayIndex)
{
Items.CopyTo(array, arrayIndex);
}
public bool Remove(int item)
{
return Items.Remove(item);
}
public int Count
{
get { return Items.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public int IndexOf(int item)
{
return Items.IndexOf(item);
}
public void Insert(int index, int item)
{
Items.Insert(index, item);
}
public void RemoveAt(int index)
{
Items.RemoveAt(index);
}
public int this[int index]
{
get { return Items[index]; }
set { Items[index] = value; }
}
#endregion
}
And an example usage:
var list = new DrivenList("foo");
list.Add(1);
list.Add(3);
list.Add(5);
var json = JsonHelper.JsonSerializer(list);
Output:
{"Items":[1,3,5],"Name":"foo"}
Apply the DataContract attribute for the class and DataMember attribute for the properties.
[DataContract]
public class DrivenList : List<int>
{
[DataMember]
public string Name {get;set;}
private DrivenList() { }
public DrivenList(string name) { this.Nname = name; }
}
I have xml like this:
<data>
<audit_values>
<audit_value>
<channel>2</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
<audit_value>
<channel>1</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
</audit_values>
</data>
And I need to deserialize it to class. But the problem is, that week will be change in future(it will be contains more elements, and name of them I dont know)
Data:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
AuditValue:
[XmlRoot("audit_value")]
public class AuditValue
{
[XmlElement("week", typeof(TVR))]
public Week Week;
}
Week:
[XmlRoot("week")]
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
var sub = reader.ReadSubtree();
do
{
if (sub.NodeType == XmlNodeType.Element)
{
string name = sub.Name;
string val = sub.ReadElementContentAsString();
Values.Add(name, Convert.ToDouble(val));
}
} while (sub.Read());
}
public void WriteXml(XmlWriter writer)
{
}
}
But after deserialization I have only one element with only one recored in dictionary Values. What I'm doing wrong?
GitHub:
https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead
I tweaked your ReadXml method based on #Matthew Whited's idea in the comments section and the following method does the job:
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
As a side note you only need XmlRoot on the actual root element not on every class so I removed it from AuditValue and Week. Also I don't know what TVR is. It didn't compile with "typeof(TVR)" so I removed it as well.
For the sake of completeness here's my version of the classes:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
public class AuditValue
{
[XmlElement("week")]
public Week Week;
}
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
public void WriteXml(XmlWriter writer)
{
}
}
You should consider using the DataContractSerializer instead of XmlSerializer and implement the IExtensibleDataObject interface on your DataContract class.
Implementing IExtensibleDataObject allows the DataContract class to persist unknown information in the ExtensionData field, which prevents it from being lost if the XML being deserialized contains unknown elements, and then is re-serialized and saved.
Your Audit class would look something like this:
[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
[DataMember(Name = "channel")]
public int Channel { get; set; }
[DataMember(Name = "week")]
public Week Week { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
[DataMember(Name = "mo_th")]
public Decimal MondayThroughThursday { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
You'll still need to update your code to deserialize the additional elements as POCOs, but at least the underlying data will be persisted until you get around to it.
Try this. XmlElement eliminates a lay of tags which you don't have. You have an extra 's' at end of values.
From
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
To
[XmlElement("audit_value")]
public AuditValue[] AuditValues { get; set; }
The error was in ReadXml method, now I changed it to this:
public void ReadXml(XmlReader reader)
{
reader.Read();
do
{
if (!reader.IsEmptyElement)
{
var name = reader.Name;
var val = Convert.ToDouble(reader.ReadElementContentAsString());
Values.Add(name, val);
}
else
{
reader.Skip();
}
} while (reader.Name != "week");
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
And I works fine. This Solution without using XElement and Linq, #Volkan Paksoy offered method with XElement which is easier to understand
I have an issue with the .NET XML Serializer where I have do different XML Elements with different names that map to the same type. Basically, the objects should be exactly the same, but I want them to have a string or enum or something that identifies which of the three possible element names were used. So here's an example:
<Body>
<MyTypeA>
<Foo>bar</Foo>
</MyTypeA>
<MyTypeB>
<Foo>bar</Foo>
</MyTypeB>
</Body>
Now, for the classes, MyTypeA and MyTypeB will both be the same type. For example:
public class Body {
public MyType MyTypeA { get; set; }
public MyType MyTypeB { get; set; }
}
public class MyType {
public string Foo { get; set; }
[XmlIgnore]
public MyTypeType { get; set; }
}
public enum MyTypeType
{
MyTypeA,
MyTypeB
}
When serializing it works fine, because I can always just ensure one way or another that the enum is set properly before serializing. But when deserializing it is not getting set and I'm not sure there's a way how.
For the record, I unfortunately don't get to set the schema, otherwise I would have built it in such a way that I didn't have this problem.
If i understood your question correctly, this might help you. Just write you XML file path on the 4th line and try it.
namespace ConsoleApplication1
{
class Program
{
//private const string xmlPath = #"C:\Users\Jumast\Desktop\StackOverflowQuestion.xml";
private const string xmlPath, // put the file path here
static Body makeBody()
{
var instance1 = new MyType() { Category = Category.MyTypeA, Foo = "bar" };
var instance2 = new MyType() { Category = Category.MyTypeB, Foo = "bar" };
return new Body(){Instance1 = instance1, Instance2 = instance2};
}
static void serializeBody(Body body, string path)
{
var ser = new DataContractSerializer(body.GetType(), body.GetType().Name, "");
using (var w = XmlWriter.Create(path, new XmlWriterSettings() { Indent = true }))
{
ser.WriteObject(w, body);
}
}
static Body deseerializeBody(string xmlPath)
{
Body deserializedBody;
var ser = new XmlSerializer(typeof(Body));
using (Stream stream = File.OpenRead(xmlPath))
{
deserializedBody = (Body)ser.Deserialize(stream);
}
return deserializedBody;
}
static void writeBodyToConsole(Body body)
{
Console.WriteLine("Instance1: " + body.Instance1);
Console.WriteLine("Instance2: " + body.Instance2);
Console.ReadKey();
}
static void Main(string[] args)
{
serializeBody(makeBody(), xmlPath);
writeBodyToConsole(deseerializeBody(xmlPath));
}
}
public class Body : IXmlSerializable
{
#region Properties
public MyType Instance1 { get; set; }
public MyType Instance2 { get; set; }
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Instance1 = new MyType(reader);
Instance2 = new MyType(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Instance1.WriteXml(writer);
Instance2.WriteXml(writer);
}
#endregion
}
public class MyType : IXmlSerializable
{
#region Fields
private Category _category;
#endregion
#region Constructors
public MyType()
{
_category = Category.nil;
Foo = string.Empty;
}
public MyType(XmlReader reader) { ReadXml(reader);}
#endregion
#region Methods
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(string.Format("Foo = {0}", Foo));
sb.Append(" , ");
sb.Append(string.Format("Category = {0}", Category));
return sb.ToString();
}
#endregion
#region Properties
public string Foo { get; set; }
public Category Category
{
get { return this._category; }
set { this._category = value; }
}
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Enum.TryParse(reader.Name, out _category);
reader.Read();
Foo = reader.ReadElementContentAsString("Foo", "");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(this.Category.ToString(), "");
writer.WriteElementString("Foo", Foo);
writer.WriteEndElement();
}
#endregion
}
public enum Category
{
MyTypeA,
MyTypeB,
nil
}
}