I have this C# 4.0 type
public class DecimalField
{
public decimal Value { get; set; }
public bool Estimate { get; set; }
}
I want to use XmlSerializer to serialize the type into
<Val Estimate="true">123</Val>
Ideally, I want to omit the Estimate attribute if its value is false. Changing Estimate to a nullable bool is acceptable.
What attributes/implementations are required to go from this type to this XML representation?
Thanks.
Not sure if you can output Estimate conditionally with attributes only. But you definitelly can implement IXmlSerializable and check Estimate value inside WriteXml method.
Here is an example
Conditionally omitting Estimate would require a lof of coding. I wouldn't go that way.
XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings() { OmitXmlDeclaration = true });
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer xml = new XmlSerializer(typeof(DecimalField));
xml.Serialize(writer, obj, ns);
-
[XmlRoot("Val")]
public class DecimalField
{
[XmlText]
public decimal Value { get; set; }
[XmlAttribute]
public bool Estimate { get; set; }
}
You can also manually serialize your class using Linq2Xml
List<XObject> list = new List<XObject>();
list.Add(new XText(obj.Value.ToString()));
if (obj.Estimate) list.Add(new XAttribute("Estimate", obj.Estimate));
XElement xElem = new XElement("Val", list.ToArray());
xElem.Save(stream);
This is about as close as you can get (an Estimate attribute is always included) without implementing IXmlSerializable:
[XmlRoot("Val")]
public class DecimalField
{
[XmlText()]
public decimal Value { get; set; }
[XmlAttribute("Estimate")]
public bool Estimate { get; set; }
}
With IXmlSerializable, your class looks like this:
[XmlRoot("Val")]
public class DecimalField : IXmlSerializable
{
public decimal Value { get; set; }
public bool Estimate { get; set; }
public void WriteXml(XmlWriter writer)
{
if (Estimate == true)
{
writer.WriteAttributeString("Estimate", Estimate.ToString());
}
writer.WriteString(Value.ToString());
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToAttribute("Estimate") && reader.ReadAttributeValue())
{
Estimate = bool.Parse(reader.Value);
}
else
{
Estimate = false;
}
reader.MoveToElement();
Value = reader.ReadElementContentAsDecimal();
}
public XmlSchema GetSchema()
{
return null;
}
}
You can test your class like this:
XmlSerializer xs = new XmlSerializer(typeof(DecimalField));
string serializedXml = null;
using (StringWriter sw = new StringWriter())
{
DecimalField df = new DecimalField() { Value = 12.0M, Estimate = false };
xs.Serialize(sw, df);
serializedXml = sw.ToString();
}
Console.WriteLine(serializedXml);
using (StringReader sr = new StringReader(serializedXml))
{
DecimalField df = (DecimalField)xs.Deserialize(sr);
Console.WriteLine(df.Estimate);
Console.WriteLine(df.Value);
}
Related
I need to serialize a class to xml. If a certain condition is met at run-time, I want to add an XML attribute to an element and assign it a value. Sometimes, the "Error" attribute will appear and sometimes it won't.
My code that serializes my objects:
public class XmlToolsRepo : IXmlTools
{
public string SerializeToXML<T>(object obj)
{
string results = null;
Encoding enc = Encoding.UTF8;
using (MemoryStream ms = new MemoryStream())
{
using (XmlTextWriter xw = new XmlTextWriter(ms, enc))
{
xw.Formatting = Formatting.None;
XmlSerializerNamespaces emptyNS = new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") });
XmlSerializer xSerializer = new XmlSerializer(typeof(T));
xSerializer.Serialize(xw, obj, emptyNS);
}
results = enc.GetString(ms.ToArray());
}
return results;
}
}
A class with a property that could have a new attribute at run-time:
[DataContract]
public class H204
{
[DataMember]
[XmlAttribute]
public string Code { get; set; }
[DataMember]
public string DW { get; set; }
}
When a condition is met I need for the XML to look like this:
<?xml version="1.0" encoding="UTF-8"?>
<H204 Code="A">
<DW Error="test" />
</H204>
Try following :
public class H204
{
[XmlAttribute(AttributeName = "Code")]
public string Code { get; set; }
[XmlElement(ElementName = "DW")]
public DW dw{ get; set; }
}
public class DW
{
[XmlAttribute(AttributeName = "Error")]
public string text { get; set; }
}
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 need to serialize my object in such a way that the properties I want, would get wrapped around CData sections.
I was hoping I could do something like this :
public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }
[DataMember]
[JsonProperty]
**[WrapCData]**
public List<Option> ListB { get; set; }
[DataMember]
public List<string> ListC { get; set; }
**[WrapCData]**
public Product Product { get; set; }
}
Is there any attribute or an implementation which could wrap my specific properties around a CData section? Existing StackOverflow answers suggest fiddling with the Entity(Class) itself. This would get really messy.
In the following thread :
How do you serialize a string as CDATA using XmlSerializer?
Philip's answer suggests to make another property and its corresponding CData property. However the property is a string. CreateCDataSection() also takes a string. I need to wrap my custom objects/lists around CDataSections. How can I do that? Any help would be appreciated. Thanks.
Sample XML for the above Order Class:
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T15:10:49.6031106+05:00</Date>
<![CDATA[
<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>
]]>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC>
<![CDATA[
<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>
]]>
</Order>
With some effort and customization, it possible to get close to what you want, however XmlSerializer will always place the CData nodes at the end of the container element. Your example XML shows the CData nodes between specific nodes of the container element. As long as you don't need this precise control, you can use How do you serialize a string as CDATA using XmlSerializer? to do nested serializations, like so:
public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }
[DataMember]
[JsonProperty]
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public List<Option> ListB { get; set; }
[DataMember]
public List<string> ListC { get; set; }
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public Product Product { get; set; }
[XmlText] // NECESSARY TO EMIT CDATA NODES
[IgnoreDataMember]
[JsonIgnore]
public XmlNode[] CDataContent
{
get
{
return XmlWrapCDataHelper.GetCDataContent(this);
}
set
{
XmlWrapCDataHelper.SetCDataContent(this, value);
}
}
}
public class Product
{
public string ProductId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
public class Option
{
public string OptionValue { get; set; }
public string OptionName { get; set; }
}
Using the following extension methods and custom attribute:
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class XmlWrapCDataAttribute : Attribute
{
public XmlWrapCDataAttribute() { this.Namespace = string.Empty; }
public XmlWrapCDataAttribute(string name) : this() { this.Name = name; }
public string Name { get; set; }
public string Namespace { get; set; }
}
public static class XmlWrapCDataHelper
{
static Tuple<PropertyInfo, XmlWrapCDataAttribute> [] XmlWrapCDataProperties(Type type)
{
return type.GetProperties()
.Where(p => p.GetGetMethod() != null && p.GetSetMethod() != null)
.Select(p => Tuple.Create(p, p.GetCustomAttribute<XmlWrapCDataAttribute>()))
.Where(p => p.Item2 != null)
.ToArray();
}
public static XmlNode[] GetCDataContent(object obj)
{
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType());
return properties.Select(p => (XmlNode)p.Item1.GetValue(obj, index).GetCData(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace)).ToArray();
}
public static void SetCDataContent(object obj, XmlNode [] nodes)
{
if (nodes == null || nodes.Length < 1)
return;
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType()).ToDictionary(p => XName.Get(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace), p => p);
var xml = "<Root>" + String.Concat(nodes.Select(c => c.Value)) + "</Root>";
foreach (var element in XElement.Parse(xml).Elements())
{
Tuple<PropertyInfo, XmlWrapCDataAttribute> pair;
if (properties.TryGetValue(element.Name, out pair))
{
var value = element.Deserialize(pair.Item1.PropertyType, element.Name.LocalName, element.Name.Namespace.NamespaceName);
pair.Item1.SetValue(obj, value, index);
}
}
}
}
public static class XmlSerializationHelper
{
public static XmlCDataSection GetCData(this object obj, string rootName, string rootNamespace)
{
return obj == null ? null : new System.Xml.XmlDocument().CreateCDataSection(obj.GetXml(XmlSerializerFactory.Create(obj.GetType(), rootName, rootNamespace)));
}
public static XCData GetCData(this object obj, XmlSerializer serializer = null)
{
return obj == null ? null : new XCData(obj.GetXml(serializer));
}
public static string GetXml(this object obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
public static object Deserialize(this XContainer element, Type type, string rootName = null, string rootNamespace = null)
{
return element.Deserialize(type, XmlSerializerFactory.Create(type, rootName, rootNamespace));
}
public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer = null)
{
using (var reader = element.CreateReader())
{
return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
}
}
public static T DeserializeXML<T>(this string xmlString, XmlSerializer serializer = null)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
}
}
public static class XmlSerializerFactory
{
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
This will parse your provided XML successfully, and in return generate XML that looks like:
<Order>
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T05:10:49.6031106-05:00</Date>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC><![CDATA[<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>]]><![CDATA[<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>]]></Order>
I have the following type:
[XmlRoot(Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace")]
public class Location
{
public int Id { get; set; }
public string Name { get; set; }
public Collection<int> DataSourceIds { get; set; }
}
I'm serializing a list of Locations to XML, resulting in the following:
<ArrayOfLocation xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyNamespace">
<Location>
<DataSourceIds xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:int>1</d3p1:int>
</DataSourceIds>
<Id>2</Id>
<Name>First</Name>
</Location>
<Location>
<DataSourceIds xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:int>1</d3p1:int>
<d3p1:int>2</d3p1:int>
<d3p1:int>3</d3p1:int>
<d3p1:int>4</d3p1:int>
</DataSourceIds>
<Id>1</Id>
<Name>Second</Name>
</Location>
</ArrayOfLocation>
I then try to deserialize this XML as follows:
var rootAttribute = new XmlRootAttribute("ArrayOfLocation")
{
Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace"
};
var serializer = new XmlSerializer(typeof(Location[]), rootAttribute);
using (var xmlReader = XmlReader.Create(new StreamReader(response.GetResponseStream())))
{
locations = (Location[])serializer.Deserialize(xmlReader);
}
This returns a list of Location objects, with every property set correctly... except DataSourceIds, which remains empty. Why isn't XmlSerializer deserializing the array of integers?
Since it was serialized with DataContractSerializer, you can deserialize it like so:
[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace")]
public class Location
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public Collection<int> DataSourceIds { get; set; }
}
And then use it like:
using (var xmlReader = XmlReader.Create(stream))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(Location[]));
var locations = (Location[])serializer.ReadObject(xmlReader);
Debug.WriteLine(DataContractSerializerHelper.GetXml(locations, serializer)); // Debug check on re-serialization, remove when not needed.
}
The XmlRoot declaration is ignored by DataContractSerializer.
Finally, a utility method for re-serializing to an XML string, for debugging purposes:
public static class DataContractSerializerHelper
{
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
public static string GetXml<T>(T obj, DataContractSerializer serializer) where T : class
{
using (var textWriter = new StringWriter())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " "; // For cosmetic purposes.
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.WriteObject(xmlWriter, obj);
}
return textWriter.ToString();
}
}
public static string GetXml<T>(T obj) where T : class
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
return GetXml(obj, serializer);
}
}
I'm trying to deserialize a reponse from a REST API.
"<FieldListDTO xmlns=\"api.playcento.com/1.0\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
<Allfield>
<FieldDTO>
<Fieldname>Mobile nr</Fieldname>
<Fieldtype>T</Fieldtype>
<Fieldvalue>003241234578</Fieldvalue>
<Fk_id_page>CP584ea74ce5ad4e2d8561d75fc6944f96</Fk_id_page>
<Id_field>FI152dcde5ef9849898b12d6a3f2cdb4ee</Id_field>
<Required>true</Required>
</FieldDTO>
</Allfield>
<Totalcount>1</Totalcount>
</FieldListDTO>"
The Field class:
namespace PlaycentoAPI.Model
{
[XmlRoot("FieldListDTO",Namespace = "api.playcento.com/1.0")]
[XmlType("FieldListDTO")]
public class FieldListDTO
{
public FieldListDTO() { }
[XmlElement("Totalcount")]
public int TotalCount { get; set; }
[XmlArray("Allfield")]
[XmlArrayItem("FieldDTO", typeof(Field))]
public Field[] Field { get; set; }
}
[XmlRoot("FieldDTO", Namespace = "api.paycento.com/1.0")]
[XmlType("FieldDTO")]
public class Field
{
public Field()
{
}
[XmlElement("Id_field")]
public string ID_Field { get; set; }
[XmlElement("Fieldtype")]
public string FieldType { get; set; }
[XmlElement("Fk_id_page")]
public string FK_ID_PAGE { get; set; }
[XmlElement("Required")]
public bool Required { get; set; }
[XmlElement("Fieldname")]
public string FieldName { get; set; }
[XmlElement("Fieldvalue")]
public string FieldValue { get; set; }
}
}
My code which calls the API and deserializes it:
string response = Helper.PerformAndReadHttpRequest(uri, "GET", "");
FieldListDTO myObject;
XmlReaderSettings settings = new XmlReaderSettings();
using (StringReader textReader = new StringReader(response))
{
using (XmlReader xmlReader = XmlReader.Create(textReader, settings))
{
XmlSerializer mySerializer = new XmlSerializer(typeof(FieldListDTO));
myObject = (FieldListDTO)mySerializer.Deserialize(xmlReader);
}
}
return myObject.Field;
In my actual response, I'm getting back 14 FieldDTO's. After deserializing the xml, FieldListDTO myObject contains TotalCount = 14 and Field is an array containing 14 Field's. But all the properties of these fields are NULL (or false).
I'm using the same method for several other API calls. I've compared the classes and the only difference that I see is that the class (Field) has an bool property. So I thought that was the problem. I've changed the bool property to a string but still all the properties were NULL after deserialization.
First thing to catch my eye is that the namespace in your FieldDTO class doesn't match the one in the XML document.
"api.paycento.com/1.0"
"api.playcento.com/1.0"