how to serialize List of CustomClass with object attribute - c#

I want to serialize CMainClass:
[XmlType("Param")]
public class CParam
{
[XmlElement]
public string Name;
[XmlElement]
public object Value;
public CParam() { }
public CParam(string name, object value)
{
Name = name;
Value = value;
}
}
public class CMainClass
{
public List<CParam> Parameters = new List<CParam>();
public CMainClass() { }
public string GetXML()
{
XmlDocument doc = new XmlDocument();
Type[] extraTypes = new Type[1];
extraTypes[0] = typeof(CParam);
XmlSerializer serializer = new XmlSerializer(typeof(CMainClass), extraTypes);
MemoryStream stream = new MemoryStream();
try
{
serializer.Serialize(stream, this);
stream.Position = 0;
doc.Load(stream);
return doc.InnerXml;
}
catch { throw; }
finally
{
stream.Close();
stream.Dispose();
}
}
}
The type of attribute Value can be various, that why object-type is used.
here is test code
CMainClass mc = new CMainClass();
mc.Parameters.Add(new CParam("number", 123));
mc.Parameters.Add(new CParam("text", "lorem ipsum"));
mc.Parameters.Add(new CParam("price", 23.50));
string s = mc.GetXML();
Console.WriteLine(s);
Console.ReadLine();
All I want is to get output in following form
<?xml version="1.0"?>
<CMainClass>
<Parameters>
<Param Name="number" Value="123" />
<Param Name="text" Value="lorem ipsum" />
<Param Name="price" Value="23.5" />
</Parameters>
</CMainClass>
instead of
<?xml version="1.0"?>
<CMainClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Parameters>
<Param>
<Name>number</Name>
<Value xsi:type="xsd:int">123</Value>
</Param>
<Param>
<Name>text</Name>
<Value xsi:type="xsd:string">lorem ipsum</Value>
</Param>
<Param>
<Name>price</Name>
<Value xsi:type="xsd:double">23.5</Value>
</Param>
</Parameters>
</CMainClass>
Is it possible? Changing property of Value from [XmlElement] to [XmlAttribute] leads to error.

If you want to use attributes, then you'll have to specify their types explicitly. The reason for the error you get when trying this is that the serializer doesn't know what type object actually is when deserializing as, unlike elements, it can't use type attributes to indicate this. You will have to change it to string instead and handle the conversion yourself.
public class Param
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
}
Also note that your GetXML method can be simplified to just this:
var serializer = new XmlSerializer(typeof(CMainClass));
using (var writer = new StringWriter())
{
serializer.Serialize(writer, this);
return writer.ToString();
}
See this fiddle for a working demo.

To serialize a class you should add [Serializable()] attribute.

Related

Map XML tags with dot(.) to C# object

I have XML file which have some tags with dot(.), now i am using "StringWriter"
to Map XML data with C#, but i am not able to handle XML tags which have dot(.) in tag name like:- "Customer.Name", how can i map this with C# class.
I had give XmlElement with my class model, but still i am not getting values map with my class.
Can any one give me suggestion.
[XmlElement(ElementName = "PARENTNAME")]
public string PARENTNAME { get; set; } //This is perfect mapped
[XmlElement(ElementName = "DISPLAYCONTACT.CONTACTNAME")]
public string DISPLAYCONTACTCONTACTNAME { get; set; } // This is not mapped
[XmlElement(ElementName = "DISPLAYCONTACT.COMPANYNAME")]
public string DISPLAYCONTACTCOMPANYNAME { get; set; }
Convert XML to C#
XmlDocument doc = new XmlDocument();
doc.LoadXml(custome‌​rString);
StringWriter sw = new StringWriter();
XmlTextWriter xw = new XmlTextWriter(sw);
doc.WriteTo(xw);
String XmlString = sw.ToString();
string xmlToJson = JsonConvert.SerializeXmlNode(doc);
var deserialized = JsonConvert.SerializeXmlNode(doc);
var custome‌​r = new CusGetResIn();
custome‌​r= JsonConvert.DeserializeObject<CusGetResIn>(deserialized);
Also Tried:-
XmlSerializer serializer = new XmlSerializer(typeof(CusGetResIn));
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(XmlString));
CusGetResIn resultingMessage = (CusGetResIn)serializer.Deserialize(memStream);
XML:-
<?xml version="1.0" encoding="UTF-8"?>
<response>
<control>
<status>success</status>
<senderid>Intacct_ISB</senderid>
<controlid>17/12/2018</controlid>
<uniqueid>false</uniqueid>
<dtdversion>3.0</dtdversion>
</control>
<operation>
<authentication>
<status>success</status>
<userid>vinit1</userid>
<companyid>FASTMORE-trial</companyid>
<locationid></locationid>
<sessiontimestamp>2018-12-19T00:41:15-08:00</sessiontimestamp>
</authentication>
<result>
<status>success</status>
<function>readByQuery</function>
<controlid>testFunctionId</controlid>
<data listtype="customer" count="31" totalcount="31" numremaining="0" resultId="">
<customer>
<RECORDNO>5</RECORDNO>
<CUSTOMERID>CUST-00101</CUSTOMERID>
<NAME>Sun Microsystems - EBC</NAME>
<ENTITY>CCUST-00101</ENTITY>
<PARENTKEY></PARENTKEY>
<PARENTID></PARENTID>
<PARENTNAME></PARENTNAME>
<DISPLAYCONTACT.CONTACTNAME>Sun Microsystems - EBC(CCUST-00101)</DISPLAYCONTACT.CONTACTNAME>
<DISPLAYCONTACT.COMPANYNAME>Sun Microsystems - EBC</DISPLAYCONTACT.COMPANYNAME>
<DISPLAYCONTACT.PREFIX></DISPLAYCONTACT.PREFIX>
<DISPLAYCONTACT.FIRSTNAME></DISPLAYCONTACT.FIRSTNAME>
<DISPLAYCONTACT.LASTNAME></DISPLAYCONTACT.LASTNAME>
<DISPLAYCONTACT.INITIAL></DISPLAYCONTACT.INITIAL>
<DISPLAYCONTACT.PRINTAS>Sun Microsystems - Executive Briefing Center</DISPLAYCONTACT.PRINTAS>
<DISPLAYCONTACT.TAXABLE>true</DISPLAYCONTACT.TAXABLE>
<DISPLAYCONTACT.TAXGROUP></DISPLAYCONTACT.TAXGROUP>
<DISPLAYCONTACT.PHONE1></DISPLAYCONTACT.PHONE1>
<DISPLAYCONTACT.PHONE2></DISPLAYCONTACT.PHONE2>
<DISPLAYCONTACT.CELLPHONE></DISPLAYCONTACT.CELLPHONE>
<DISPLAYCONTACT.PAGER></DISPLAYCONTACT.PAGER>
<DISPLAYCONTACT.FAX></DISPLAYCONTACT.FAX>
<DISPLAYCONTACT.EMAIL1>sevans#intacct.com</DISPLAYCONTACT.EMAIL1>
<DISPLAYCONTACT.EMAIL2></DISPLAYCONTACT.EMAIL2>
<DISPLAYCONTACT.URL1></DISPLAYCONTACT.URL1>
<DISPLAYCONTACT.URL2></DISPLAYCONTACT.URL2>
<DISPLAYCONTACT.VISIBLE>true</DISPLAYCONTACT.VISIBLE>
<DISPLAYCONTACT.MAILADDRESS.ADDRESS1>1245 Williams Lane</DISPLAYCONTACT.MAILADDRESS.ADDRESS1>
<DISPLAYCONTACT.MAILADDRESS.ADDRESS2></DISPLAYCONTACT.MAILADDRESS.ADDRESS2>
<DISPLAYCONTACT.MAILADDRESS.CITY>San Jose</DISPLAYCONTACT.MAILADDRESS.CITY>
<DISPLAYCONTACT.MAILADDRESS.STATE>CA</DISPLAYCONTACT.MAILADDRESS.STATE>
<DISPLAYCONTACT.MAILADDRESS.ZIP>95112</DISPLAYCONTACT.MAILADDRESS.ZIP>
<DISPLAYCONTACT.MAILADDRESS.COUNTRY>USA</DISPLAYCONTACT.MAILADDRESS.COUNTRY>
<DISPLAYCONTACT.MAILADDRESS.COUNTRYCODE></DISPLAYCONTACT.MAILADDRESS.COUNTRYCODE>
<DISPLAYCONTACT.MAILADDRESS.LATITUDE></DISPLAYCONTACT.MAILADDRESS.LATITUDE>
<DISPLAYCONTACT.MAILADDRESS.LONGITUDE></DISPLAYCONTACT.MAILADDRESS.LONGITUDE>
<DISPLAYCONTACT.STATUS>active</DISPLAYCONTACT.STATUS>
<TERMNAME>2/10 Net30</TERMNAME>
<TERMVALUE>2:10:1#30#1%:W:</TERMVALUE>
<CUSTREPID>EMP-002</CUSTREPID>
<CUSTREPNAME>Joanna Drake</CUSTREPNAME>
<RESALENO></RESALENO>
<TAXID></TAXID>
<CREDITLIMIT></CREDITLIMIT>
<TOTALDUE>3525172.72</TOTALDUE>
<COMMENTS></COMMENTS>
<ACCOUNTLABEL></ACCOUNTLABEL>
<ARACCOUNT>4000</ARACCOUNT>
<ARACCOUNTTITLE>Sales</ARACCOUNTTITLE>
<LAST_INVOICEDATE>10/01/2012</LAST_INVOICEDATE>
<LAST_STATEMENTDATE></LAST_STATEMENTDATE>
<DELIVERY_OPTIONS>Print#~#E-Mail</DELIVERY_OPTIONS>
<TERRITORYID></TERRITORYID>
<SHIPPINGMETHOD>Delivery</SHIPPINGMETHOD>
<CUSTTYPE>Corporate</CUSTTYPE>
<GLGRPKEY></GLGRPKEY>
<GLGROUP></GLGROUP>
<PRICESCHEDULE></PRICESCHEDULE>
<DISCOUNT></DISCOUNT>
<PRICELIST></PRICELIST>
<VSOEPRICELIST></VSOEPRICELIST>
<CURRENCY></CURRENCY>
<CONTACTINFO.CONTACTNAME></CONTACTINFO.CONTACTNAME>
<CONTACTINFO.PREFIX></CONTACTINFO.PREFIX>
<CONTACTINFO.FIRSTNAME></CONTACTINFO.FIRSTNAME>
<CONTACTINFO.INITIAL></CONTACTINFO.INITIAL>
<CONTACTINFO.LASTNAME></CONTACTINFO.LASTNAME>
<CONTACTINFO.COMPANYNAME></CONTACTINFO.COMPANYNAME>
<CONTACTINFO.PRINTAS></CONTACTINFO.PRINTAS>
<CONTACTINFO.PHONE1></CONTACTINFO.PHONE1>
<CONTACTINFO.PHONE2></CONTACTINFO.PHONE2>
<CONTACTINFO.CELLPHONE></CONTACTINFO.CELLPHONE>
<CONTACTINFO.PAGER></CONTACTINFO.PAGER>
<CONTACTINFO.FAX></CONTACTINFO.FAX>
<CONTACTINFO.EMAIL1></CONTACTINFO.EMAIL1>
<CONTACTINFO.EMAIL2></CONTACTINFO.EMAIL2>
<CONTACTINFO.URL1></CONTACTINFO.URL1>
<CONTACTINFO.URL2></CONTACTINFO.URL2>
<CONTACTINFO.VISIBLE></CONTACTINFO.VISIBLE>
<CONTACTINFO.MAILADDRESS.ADDRESS1></CONTACTINFO.MAILADDRESS.ADDRESS1>
<CONTACTINFO.MAILADDRESS.ADDRESS2></CONTACTINFO.MAILADDRESS.ADDRESS2>
<CONTACTINFO.MAILADDRESS.CITY></CONTACTINFO.MAILADDRESS.CITY>
<CONTACTINFO.MAILADDRESS.STATE></CONTACTINFO.MAILADDRESS.STATE>
<CONTACTINFO.MAILADDRESS.ZIP></CONTACTINFO.MAILADDRESS.ZIP>
<CONTACTINFO.MAILADDRESS.COUNTRY></CONTACTINFO.MAILADDRESS.COUNTRY>
<CONTACTINFO.MAILADDRESS.COUNTRYCODE></CONTACTINFO.MAILADDRESS.COUNTRYCODE>
<SHIPTO.CONTACTNAME></SHIPTO.CONTACTNAME>
<SHIPTO.PREFIX></SHIPTO.PREFIX>
<SHIPTO.FIRSTNAME></SHIPTO.FIRSTNAME>
<SHIPTO.INITIAL></SHIPTO.INITIAL>
<SHIPTO.LASTNAME></SHIPTO.LASTNAME>
<SHIPTO.COMPANYNAME></SHIPTO.COMPANYNAME>
<SHIPTO.PRINTAS></SHIPTO.PRINTAS>
<SHIPTO.TAXABLE></SHIPTO.TAXABLE>
<SHIPTO.TAXGROUP></SHIPTO.TAXGROUP>
<SHIPTO.PHONE1></SHIPTO.PHONE1>
<SHIPTO.PHONE2></SHIPTO.PHONE2>
<SHIPTO.CELLPHONE></SHIPTO.CELLPHONE>
<SHIPTO.PAGER></SHIPTO.PAGER>
<SHIPTO.FAX></SHIPTO.FAX>
<SHIPTO.EMAIL1></SHIPTO.EMAIL1>
<SHIPTO.EMAIL2></SHIPTO.EMAIL2>
<SHIPTO.URL1></SHIPTO.URL1>
<SHIPTO.URL2></SHIPTO.URL2>
<SHIPTO.VISIBLE></SHIPTO.VISIBLE>
<SHIPTO.MAILADDRESS.ADDRESS1></SHIPTO.MAILADDRESS.ADDRESS1>
<SHIPTO.MAILADDRESS.ADDRESS2></SHIPTO.MAILADDRESS.ADDRESS2>
<SHIPTO.MAILADDRESS.CITY></SHIPTO.MAILADDRESS.CITY>
<SHIPTO.MAILADDRESS.STATE></SHIPTO.MAILADDRESS.STATE>
<SHIPTO.MAILADDRESS.ZIP></SHIPTO.MAILADDRESS.ZIP>
<SHIPTO.MAILADDRESS.COUNTRY></SHIPTO.MAILADDRESS.COUNTRY>
<SHIPTO.MAILADDRESS.COUNTRYCODE></SHIPTO.MAILADDRESS.COUNTRYCODE>
<BILLTO.CONTACTNAME></BILLTO.CONTACTNAME>
<BILLTO.PREFIX></BILLTO.PREFIX>
<BILLTO.FIRSTNAME></BILLTO.FIRSTNAME>
<BILLTO.INITIAL></BILLTO.INITIAL>
<BILLTO.LASTNAME></BILLTO.LASTNAME>
<BILLTO.COMPANYNAME></BILLTO.COMPANYNAME>
<BILLTO.PRINTAS></BILLTO.PRINTAS>
<BILLTO.TAXABLE></BILLTO.TAXABLE>
<BILLTO.TAXGROUP></BILLTO.TAXGROUP>
<BILLTO.PHONE1></BILLTO.PHONE1>
<BILLTO.PHONE2></BILLTO.PHONE2>
<BILLTO.CELLPHONE></BILLTO.CELLPHONE>
<BILLTO.PAGER></BILLTO.PAGER>
<BILLTO.FAX></BILLTO.FAX>
<BILLTO.EMAIL1></BILLTO.EMAIL1>
<BILLTO.EMAIL2></BILLTO.EMAIL2>
<BILLTO.URL1></BILLTO.URL1>
<BILLTO.URL2></BILLTO.URL2>
<BILLTO.VISIBLE></BILLTO.VISIBLE>
<BILLTO.MAILADDRESS.ADDRESS1></BILLTO.MAILADDRESS.ADDRESS1>
<BILLTO.MAILADDRESS.ADDRESS2></BILLTO.MAILADDRESS.ADDRESS2>
<BILLTO.MAILADDRESS.CITY></BILLTO.MAILADDRESS.CITY>
<BILLTO.MAILADDRESS.STATE></BILLTO.MAILADDRESS.STATE>
<BILLTO.MAILADDRESS.ZIP></BILLTO.MAILADDRESS.ZIP>
<BILLTO.MAILADDRESS.COUNTRY></BILLTO.MAILADDRESS.COUNTRY>
<BILLTO.MAILADDRESS.COUNTRYCODE></BILLTO.MAILADDRESS.COUNTRYCODE>
<STATUS>active</STATUS>
<ONETIME>false</ONETIME>
<CUSTMESSAGEID></CUSTMESSAGEID>
<ONHOLD>false</ONHOLD>
<PRCLST_OVERRIDE>C</PRCLST_OVERRIDE>
<OEPRCLSTKEY></OEPRCLSTKEY>
<OEPRICESCHEDKEY></OEPRICESCHEDKEY>
<ENABLEONLINECARDPAYMENT>true</ENABLEONLINECARDPAYMENT>
<ENABLEONLINEACHPAYMENT>true</ENABLEONLINEACHPAYMENT>
<VSOEPRCLSTKEY></VSOEPRCLSTKEY>
<WHENMODIFIED>12/18/2018 16:07:40</WHENMODIFIED>
<ARINVOICEPRINTTEMPLATEID></ARINVOICEPRINTTEMPLATEID>
<OEQUOTEPRINTTEMPLATEID></OEQUOTEPRINTTEMPLATEID>
<OEORDERPRINTTEMPLATEID></OEORDERPRINTTEMPLATEID>
<OELISTPRINTTEMPLATEID></OELISTPRINTTEMPLATEID>
<OEINVOICEPRINTTEMPLATEID></OEINVOICEPRINTTEMPLATEID>
<OEADJPRINTTEMPLATEID></OEADJPRINTTEMPLATEID>
<OEOTHERPRINTTEMPLATEID></OEOTHERPRINTTEMPLATEID>
<WHENCREATED>01/01/1970 00:00:00</WHENCREATED>
<CREATEDBY></CREATEDBY>
<MODIFIEDBY>1</MODIFIEDBY>
<OBJECTRESTRICTION>Unrestricted</OBJECTRESTRICTION>
<DISPLAYCONTACTKEY>38</DISPLAYCONTACTKEY>
<CONTACTKEY></CONTACTKEY>
<SHIPTOKEY></SHIPTOKEY>
<BILLTOKEY></BILLTOKEY>
<CUSTREPKEY>2</CUSTREPKEY>
<SHIPVIAKEY>1</SHIPVIAKEY>
<TERRITORYKEY></TERRITORYKEY>
<TERMSKEY>1</TERMSKEY>
<ACCOUNTLABELKEY></ACCOUNTLABELKEY>
<ACCOUNTKEY>25</ACCOUNTKEY>
<CUSTTYPEKEY>1</CUSTTYPEKEY>
<PRICESCHEDULEKEY></PRICESCHEDULEKEY>
<OFFSETGLACCOUNTNO></OFFSETGLACCOUNTNO>
<OFFSETGLACCOUNTNOTITLE></OFFSETGLACCOUNTNOTITLE>
<ADVBILLBY></ADVBILLBY>
<ADVBILLBYTYPE></ADVBILLBYTYPE>
<SUPDOCID></SUPDOCID>
<MEGAENTITYKEY></MEGAENTITYKEY>
<MEGAENTITYID></MEGAENTITYID>
<MEGAENTITYNAME></MEGAENTITYNAME>
<RESTRICTEDLOCATIONS></RESTRICTEDLOCATIONS>
<RESTRICTEDDEPARTMENTS></RESTRICTEDDEPARTMENTS>
</customer></data>
</result>
</operation>
</response>
Please help me.
Quick test shows XmlSerializer can handle this pretty easily
Test class:
[XmlRoot]
public class Test
{
[XmlElement(ElementName="Foo.Alpha")]
public string Alpha {get;set;}
[XmlElement(ElementName="Foo.Beta")]
public string Beta {get;set;}
}
Example method:
private static void Main()
{
var src = #"<Test>
<Foo.Alpha>value 1</Foo.Alpha>
<Foo.Beta>value 2</Foo.Beta>
</Test>";
using (var sreader = new StringReader(src))
using (var reader = XmlReader.Create(sreader))
{
var serializer = new XmlSerializer(typeof(Test));
var test = (Test)serializer.Deserialize(reader);
Console.WriteLine(test.Alpha);
Console.WriteLine(test.Beta);
}
}
Update: since the XML document you are processing is very large, you should consider what you actually need to do. If you need to manipulate the entire document as objects then the approach taken in JP Hellemons's answer may be the way to go. If you are only interested in certain fields, then it may be better to load the XML into an XDocument or XmlDocument and extract the fields you are interested in from that, or even write a forward-only parser using XmlReader to do the same thing.
I have pasted your XML in an XMLFile1.xml and pasted it in Visual Studio to generate classes, so this will be a lot of autogenerated code... warning
could not fit auto gen code...
Body is limited to 30000 characters; you entered 93977
Full code here:
https://pastebin.com/VxzNUjsv
Smaller version:
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<RECORDNO>5</RECORDNO>
<CUSTOMERID>CUST-00101</CUSTOMERID>
<PARENTNAME>parent</PARENTNAME>
<DISPLAYCONTACT.CONTACTNAME>Sun Microsystems - EBC(CCUST-00101)</DISPLAYCONTACT.CONTACTNAME>
<DISPLAYCONTACT.COMPANYNAME>Sun Microsystems - EBC</DISPLAYCONTACT.COMPANYNAME>
</customer>
and C# code:
class Program
{
static void Main(string[] args)
{
using (var sreader = new StringReader(File.ReadAllText(#"C:\Users\JP\source\repos\soXmlParsing\soXmlParsing\XMLFile1.xml")))
using (var reader = XmlReader.Create(sreader))
{
var serializer = new XmlSerializer(typeof(customer));
var test = (customer)serializer.Deserialize(reader);
Console.WriteLine(test.PARENTNAME);
Console.WriteLine(test.DISPLAYCONTACTCONTACTNAME);
}
}
}
// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class customer
{
private byte rECORDNOField;
private string cUSTOMERIDField;
private string pARENTNAMEField;
private string dISPLAYCONTACTCONTACTNAMEField;
private string dISPLAYCONTACTCOMPANYNAMEField;
/// <remarks/>
public byte RECORDNO
{
get
{
return this.rECORDNOField;
}
set
{
this.rECORDNOField = value;
}
}
/// <remarks/>
public string CUSTOMERID
{
get
{
return this.cUSTOMERIDField;
}
set
{
this.cUSTOMERIDField = value;
}
}
/// <remarks/>
public string PARENTNAME
{
get
{
return this.pARENTNAMEField;
}
set
{
this.pARENTNAMEField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("DISPLAYCONTACT.CONTACTNAME")]
public string DISPLAYCONTACTCONTACTNAME
{
get
{
return this.dISPLAYCONTACTCONTACTNAMEField;
}
set
{
this.dISPLAYCONTACTCONTACTNAMEField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("DISPLAYCONTACT.COMPANYNAME")]
public string DISPLAYCONTACTCOMPANYNAME
{
get
{
return this.dISPLAYCONTACTCOMPANYNAMEField;
}
set
{
this.dISPLAYCONTACTCOMPANYNAMEField = value;
}
}
}

XML changes after Serialization

I noticed that my xml changes after serialization. e.g.
* the ns in the start of the Message element disappears
* xmlns:ns attribute becomes xmlns
* there are new attributes added in Message element - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" and xmlns:xsd="http://www.w3.org/2001/XMLSchema"
* new attribute in Header element - xmlns
How can I keep the original form of the xml and prevent these attributes from being added?
Here's how the original XML looks like:
<?xml version="1.0" encoding="UTF-8"?>
<ns:Message xmlns:ns="http://example.com">
<Header version="1.0">
<Sender>3015207400109</Sender>
<Receiver>8711200999903</Receiver>
<MessageID>000D2613F64AC021ED783C084735EC78E53</MessageID>
<CreationDateTime>2017-03-21T08:00:47Z</CreationDateTime>
</Header>
</ns:Message>
And here's the xml after it has been serialized:
<?xml version="1.0" encoding="UTF-8"?>
<Message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://example.com">
<Header version="1.0" xmlns="">
<Sender>3015207400109</Sender>
<Receiver>8711200999903</Receiver>
<MessageID>000D2613F64AC021ED783C084735EC78E53</MessageID>
<CreationDateTime>2017-03-21T08:00:47Z</CreationDateTime>
</Header>
</Message>
The code below is the (generated) class that represents the Message xml.
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://example.com")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://example.com", IsNullable = false)]
public partial class Message
{
private Header headerField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Namespace = "")]
public Header Header
{
get
{
return this.headerField;
}
set
{
this.headerField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Header
{
private ulong senderField;
private ulong receiverField;
private string messageIDField;
private System.DateTime creationDateTimeField;
private decimal versionField;
/// <remarks/>
public ulong Sender
{
get
{
return this.senderField;
}
set
{
this.senderField = value;
}
}
/// <remarks/>
public ulong Receiver
{
get
{
return this.receiverField;
}
set
{
this.receiverField = value;
}
}
/// <remarks/>
public string MessageID
{
get
{
return this.messageIDField;
}
set
{
this.messageIDField = value;
}
}
/// <remarks/>
public System.DateTime CreationDateTime
{
get
{
return this.creationDateTimeField;
}
set
{
this.creationDateTimeField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
}
}
}
The two XML files you show are semantically identical. Thus, I'd recommend not worrying about the fact that XmlSerializer inserts XML standard namespaces or chooses a different prefixing scheme than was used in the original file.
If, for whatever reason, you must suppress output of the standard namespaces and must preserve the prefixing scheme of the original file, here's what you can do.
Firstly, to omit the xsi and xsd namespaces at the root level, follow the instructions from Omitting all xsi and xsd namespaces when serializing an object in .NET?:
var s = new XmlSerializer(objectToSerialize.GetType());
var ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);
Next, in order to keep the original form of the xml you must first somehow capture the actual XML namespaces and prefixes encountered while reading the file and save them in the Message class somewhere for reuse later. Luckily XmlSerializer does support this: you can add an XmlSerializerNamespaces valued public property or field to Message and mark it with [XmlNamespaceDeclarations]. This member will now capture namespaces encountered during deserialization, and cause those namespaces to be added back during serialization.
Putting these two ideas together, you can modify your Message type as follows:
public partial class Message
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces XmlFileNamespaces { get; set; }
/// <summary>
/// returns a XmlSerializerNamespaces to use when serializing a Message as the root XML object.
/// If Message was previously deserialized from XML, the actual namespaces observed will be returned.
/// Otherwise, a default will be returned that suppresses output of the xmlns:xsi and xmlns:xsd namespace attributes.
/// </summary>
[XmlIgnore]
public XmlSerializerNamespaces XmlRootNamespaces
{
get
{
if (XmlFileNamespaces != null)
return XmlFileNamespaces;
var xmlNamespaces = new XmlSerializerNamespaces();
xmlNamespaces.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
// xmlNamespaces.Add("ns", "http://example.com"); // Or, if you prefer, add this namespace as well as disabling xmlns:xsi and xmlns:xsd.
return xmlNamespaces;
}
}
}
And serialize from and to XML as follows:
var message = xml.LoadFromXml<Message>();
var reserializedXml = message.GetXml(message.XmlRootNamespaces);
Using the following extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
}
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns)
{
using (var textWriter = new Utf8StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
// http://stackoverflow.com/questions/3862063/serializing-an-object-as-utf-8-xml-in-net
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
}
Prototype fiddle.

Can I have null attribute and other attribute at the same tag in XML created by XSD C# generated class?

I have a bunch of C# classes, which are auto generated from an XSD. Then I generate XML files based on those C# classes. Nothing existing so far.
The problem:
The generated XML files are going through validation and the validation requires an extra attribute to all XML tags with xsi:nil="true". Basically the tags should look like : <testTag.01 xsi:nil="true" NV="123123" />, but I can't achieve that in C#. My code is:
if (myObject.TestTag.HasValue)
{
t.testTag01 = new testTag01();
t.testTag01.Value = myObject.TestTag.Value;
}
//else
//{
// t.testTag01 = new testTag01();
// t.testTag01.NV = "123123";//Not Recorded
//}
This code generates <testTag.01>SomeValue</testTag.01> or <testTag.01 xsi:nil="true"/>.
If I uncomment the ELSE, the result would be: <testTag.01>SomeValue</testTag.01> or <testTag.01 NV="123123" />.
So I have no idea how to get to the format, which is required by the validation tool. Any ideas ?
P.S.
Here is the auto-generated C# class:
/// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd",
"4.0.30319.33440")] [System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true,
Namespace="http://www.blabla.org")]
public partial class testTag01 {
private string nvField;
private SomeEnum valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string NV {
get {
return this.nvField;
}
set {
this.nvField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public SomeEnum Value {
get {
return this.valueField;
}
set {
this.valueField = value;
}
} }
I wouldn't like to alter that part, but I understand it is impossible without doing it. Also I have tried to set SomeEnum to be Nullable. public SomeEnum? Value, but is throwing an exception:
Cannot serialize member 'Value' of type System.Nullable`1[]. XmlAttribute/XmlText cannot be used to encode complex types.
XmlSerializer doesn't directly support binding to elements that simultaneously have xsi:nil="true" along with other attribute values; see Xsi:nil Attribute Binding Support: The nil attribute and other attributes.
Thus, you need to emit the attribute manually.
If you want to be able to generate an element with no content and two attributes, one named NV and the other always being xsi:nil="true", you can modify your testTag01 class to have the NV property as well as a synthetic property having the correct namespace and name:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return "true"; } set { } }
}
If you sometimes want to have xsi:nil="true" but at other times want the element to have content corresponding to your SomeEnum, you need to do something a bit more complicated, since the xsi:nil="true" must be suppressed when the element has content:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return SomeEnum == null ? "true" : null; } set { } }
public bool ShouldSerializeNil() { return SomeEnum == null; }
[XmlIgnore]
public SomeEnum? SomeEnum { get; set; }
[XmlText]
public string SomeEnumText
{
get
{
if (SomeEnum == null)
return null;
return SomeEnum.Value.ToString();
}
set
{
// See here if one needs to parse XmlEnumAttribute attributes
// http://stackoverflow.com/questions/3047125/retrieve-enum-value-based-on-xmlenumattribute-name-value
value = value.Trim();
if (string.IsNullOrEmpty(value))
SomeEnum = null;
else
{
try
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, false);
}
catch (Exception)
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, true);
}
}
}
}
}
(An element that simultaneously has both xsi:nil="true" and content would be a violation of the XML standard; hopefully you don't have that.)
Then use it like:
public class TestClass
{
[XmlElement("testTag.01")]
public testTag01 TestTag { get; set; }
public static void Test()
{
Test(new TestClass { TestTag = new testTag01 { NV = "123123" } });
Test(new TestClass { TestTag = new testTag01 { NV = "123123", SomeEnum = SomeEnum.SomeValue } });
}
private static void Test(TestClass test)
{
var xml = test.GetXml();
var test2 = xml.LoadFromXML<TestClass>();
Console.WriteLine(test2.GetXml());
Debug.WriteLine(test2.GetXml());
if (test2.TestTag.NV != test.TestTag.NV)
{
throw new InvalidOperationException("test2.TestTag.NV != test.TestTag.NV");
}
}
}
The XML output looks like:
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123" xsi:nil="true" />
</TestClass>
Or
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123">SomeValue</testTag.01>
</TestClass>
Prototype fiddle using these extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serializer = null)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns = null, XmlWriterSettings settings = null, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
settings = settings ?? new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(typeof(T))).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
As expected there is no solution for that case out of the box, so I improvise a bit and achieved my goal in a post processing logic.
I am parsing the generated XML and if I am looking for a node with xsi:nil attribute, but without NV attribute - I add NV attribute with default value.
Same for the nodes with NV attribute, but no xsi:nil.
Here is the code:
XmlDocument doc = new XmlDocument();// instantiate XmlDocument and load XML from file
doc.Load("somepath.xml");
//Get the nodes with NV attribute(using XPath) and add xsi:nill to that nodes
XmlNodeList nodes = doc.SelectNodes("//*[#NV]");
foreach (XmlNode node in nodes)
{
XmlAttribute nilAttr = doc.CreateAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance");
nilAttr.Value = "true";
node.Attributes.Append(nilAttr);
}
//Get the nodes with xsi:nill attribute(using XPath) and add NV with default value to that nodes
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
XmlNodeList nilNodes = doc.SelectNodes("//*[#xsi:nil]", nsManager);
foreach (XmlNode node in nilNodes)
{
XmlAttribute nvAttr = doc.CreateAttribute("NV");
nvAttr.Value = "7701003";
node.Attributes.Append(nvAttr);
}
doc.Save("somepath.xml");
The upper answer makes totally sense, but since these classes are auto-generated I will do it my way with the post processing, cause if the provider changes the XSD schema, my solution doesn't need any extra work. Thanks anyway.

How to create sets of the serialized objects C#

There are various types, in a special case which can be configured in different ways. How to serialize them?
[Serializable]
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
object _schemaVersion;
[XmlElement("SchemaVersion")]
public object SchemaVersion
{
get { return _schemaVersion; }
set { _schemaVersion = value; }
}
List<object> _test;
[XmlElement("Test")]
public List<object> Test
{
get { return _test; }
set { _test = value; }
}
public RootXml()
{
}
}
I.e. root can include different objects, and they have to be serialized...
I have a xml-format approximately of such
look:
<?xml version="1.0" encoding="windows-1251"?>
<RootXml>
<SchemaVersion Number="" />
<Report Code="">
<Period Code="" Date="">
<Source ClassCode="" Code="">
<Form Code="">
<Column Num="1" Name="" />
<Column Num="2" Name="" />
<Column Num="3" Name="" />
<Document>
<Data code="11" />
<Data code="12">
<Px Num="1" Value="1" />
<Px Num="2" Value="1" />
<Px Num="4" Value="2" />
<Px Num="5" Value="2" />
</Data>
<Data code="13" />
</Document>
</Form>
</Source>
</Period>
</Report>
</RootXml>
In which some elements can change a little (Document, Document with tags, Document with the status, etc.),
included in others (for example, report incl. in scheme) ... and do not know how to change in the future.
I want to construct a set of "formats" which will also have various components, to be substituted...
Maybe for this purpose you shouldn't use serialization, and to define
set of attributes, and a reflection to process objects and to form xml (approximately just as XmlSerializer)???
You are trying to serialize and deserialize data with polymorphic fields. You have a few options here:
If you know in advance all possible types that might be encountered in a polymorphic field, you can use attributes to tell XmlSerializer how to serialize and deserialize each type. In particular, for a polymorphic field, apply [XmlElement("DerivedElementName", Type = typeof(DerivedElementType))] for every derived type that might be encountered.
For instance, simplifying your RootXml class somewhat, the following allows for two different types of report to be serialized:
[XmlRoot("Report", Namespace = "")]
public class Report
{
[XmlAttribute]
public string Code { get; set; }
[XmlElement]
public decimal TotalCost { get; set; }
}
[XmlRoot("DifferentReport", Namespace = "fuuuu")]
public class DifferentReport
{
public DifferentReport() { }
public DifferentReport(string code, string value)
{
this.Code = code;
this.Value = value;
}
[XmlAttribute]
public string Code { get; set; }
[XmlText]
public string Value { get; set; }
}
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
object _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public object Data
{
get { return _test; }
set { _test = value; }
}
}
And then later, both of the following can be serialized and deserialized:
var root1 = new RootXml { Data = new Report { Code = "a code", TotalCost = (decimal)101.01 } };
var root2 = new RootXml { Data = new DifferentReport { Code = "a different code", Value = "This is the value of the report" } };
Note that you can use the same technique with polymorphic lists, in which case the serializer will expect sequences of elements with the specified names:
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
List<object> _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public List<object> Data
{
get { return _test; }
set { _test = value; }
}
}
If the XML could be anything and you don't know what it might contain (because you must deserialize XML from the future versions and reserialize it without data loss, for example) you may need to load your XML into an XDocument then manually search for data using Linq-to-XML. For information on how to do this, see here: Basic Queries (LINQ to XML).
You could adopt a hybrid approach where you load the XML into an XDocument, then deserialize and serialize familiar portions with XmlSerializer, using the following extension methods:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement Serialize<T>(this T obj, bool omitStandardNamespaces = true)
{
return obj.Serialize(new XmlSerializer(obj.GetType()), omitStandardNamespaces);
}
public static XElement Serialize<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces = true)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(writer, obj, ns);
}
return doc.Root;
}
}
Then use them to pick out and deserialize known portions of your XML as follows:
var doc = XDocument.Parse(xml);
var reportElement = doc.Root.Element("Report");
if (reportElement != null)
{
var report1 = doc.Root.Element("Report").Deserialize<Report>();
// Do something with the report.
// Create a different report
var differentReport = new DifferentReport { Code = report1.Code + " some more code", Value = "This is the value of the report" };
var differentElement = differentReport.Serialize();
reportElement.AddAfterSelf(differentElement);
reportElement.Remove();
}
OK, given that you are using c# 2.0, you can load your Xml into an XmlDocument and use it as described here: Process XML Data Using the DOM Model. This is a precursor API to Linq-to-XML and is somewhat harder to work with -- but nevertheless totally functional.
You can also adopt the hybrid approach and use XmlSerializer to deserialize and re-serialize known chunks of an XmlDocument. Here are some extension methods for this purpose -- but since you're using c# 2.0, you must remove the this keyword:
public static class XmlNodeExtensions
{
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent)
{
return SerializeToXmlElement(o, parent, new XmlSerializer(o.GetType()));
}
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent, XmlSerializer serializer)
{
int oldCount = parent.ChildNodes.Count;
XPathNavigator navigator = parent.CreateNavigator();
using (XmlWriter writer = navigator.AppendChild())
{
writer.WriteComment(""); // Kludge suggested here: https://social.msdn.microsoft.com/Forums/en-US/9ff20a3c-913d-4c6f-a18a-c10040290862/how-to-xmlserialize-directly-into-xmldocument?forum=asmxandxml
serializer.Serialize(writer, o);
}
XmlElement returnedElement = null;
for (int i = parent.ChildNodes.Count - 1; i >= oldCount; i--)
{
XmlComment comment = parent.ChildNodes[i] as XmlComment;
if (comment != null)
{
parent.RemoveChild(comment);
}
else
{
returnedElement = (parent.ChildNodes[i] as XmlElement) ?? returnedElement;
}
}
return returnedElement;
}
public static XmlDocument SerializeToXmlDocument<T>(this T o)
{
return SerializeToXmlDocument(o, new XmlSerializer(o.GetType()));
}
public static XmlDocument SerializeToXmlDocument<T>(this T o, XmlSerializer serializer)
{
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
serializer.Serialize(writer, o);
return doc;
}
public static T Deserialize<T>(this XmlElement element)
{
return Deserialize<T>(element, new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XmlElement element, XmlSerializer serializer)
{
using (var reader = new XmlNodeReader(element))
return (T)serializer.Deserialize(reader);
}
}
Given those methods, you can do things like:
// Load the document from XML
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// Find all nodes with name "Report"
foreach (XmlElement reportNode in doc.SelectNodes("/RootXml/Report"))
{
// Deserialize as a Report
Report report = XmlNodeExtensions.Deserialize<Report>(reportNode);
// Do something with it
// Create a new Report, based on the original report.
DifferentReport differentReport = new DifferentReport(report.Code + " some more code", "This is the value of the report"); ;
// Add the new report to the children of RootXml
XmlElement newNode = XmlNodeExtensions.SerializeToXmlElement(differentReport, (XmlElement)reportNode.ParentNode);
}
As you can see this is quite similar to what is possible with Linq-to-XML.

XmlSerializer List Item Element Name

I have a class PersonList
[XmlRoot("Persons")]
PersonList : List<Human>
when I serialize this to XML, by default it will produce something like this:
<Persons>
<Human>...</Human>
<Human>...</Human>
</Persons>
My question is what needs to be done in order to change element Human to Person in the output? so the output would be :
<Persons>
<Person>...</Person>
<Person>...</Person>
</Persons>
and, how to deserialize the above XML to the PersonList class object?
Per Nick's advice, Here is my testing code:
[XmlRoot("Persons")]
public class Persons : List<Human>
{
}
[XmlRoot("Person")]
public class Human
{
public Human()
{
}
public Human(string name)
{
Name = name;
}
[XmlElement("Name")]
public string Name { get; set; }
}
void TestXmlSerialize()
{
Persons personList = new Persons();
personList.Add(new Human("John"));
personList.Add(new Human("Peter"));
try
{
using (StringWriter writer = new StringWriter())
{
XmlSerializer serializer = new XmlSerializer(typeof(Persons));
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
XmlWriter xmlWriter = XmlWriter.Create(writer, settings);
serializer.Serialize(xmlWriter, personList, namespaces);
Console.Out.WriteLine(writer.ToString());
}
}
catch (Exception e)
{
Console.Out.WriteLine( e.ToString());
}
}
The output of the testing code is:
<Persons>
<Human>
<Name>John</Name>
</Human>
<Human>
<Name>Peter</Name>
</Human>
</Persons>
As the output shows, the [XmlRoot("Person")] on Human does not change the tag to Person from Human.
Mark your class with the following attributes:
[XmlType("Account")]
[XmlRoot("Account")]
The XmlType attribute will result in the output requested in the OP. Per the documentation:
Controls the XML schema that is generated when the attribute target is serialized by the XmlSerializer
I don't think there is a way for you to control the name of the generated array elements.
If you can however wrap the Persons collection inside another class you will then have complete control over the generated output using XmlArrayAttribute and XmlArrayItemAttribute.
If you cannot create this new class you can resort to implementing IXmlSerializable, but this is much more complex.
An example for the first alternative follows:
[XmlRoot("Context")]
public class Context
{
public Context() { this.Persons = new Persons(); }
[XmlArray("Persons")]
[XmlArrayItem("Person")]
public Persons Persons { get; set; }
}
public class Persons : List<Human> { }
public class Human
{
public Human() { }
public Human(string name) { Name = name; }
public string Name { get; set; }
}
class Program
{
public static void Main(string[] args)
{
Context ctx = new Context();
ctx.Persons.Add(new Human("john"));
ctx.Persons.Add(new Human("jane"));
var writer = new StringWriter();
new XmlSerializer(typeof(Context)).Serialize(writer, ctx);
Console.WriteLine(writer.ToString());
}
}
I had the identical problem with my serializer. None of the answers above worked exactly. I found that the XmlRoot attribute on the Human class is plainly ignored because it isn't the root element of the document. Wrapping the list in a context object wasn't an option for me because I can't change the XML schema. The solution is to change up the Persons class. Instead of subclassing a generic list, you wrap it in an object and change how it is serialized. See the sample code below:
[XmlRoot("Persons")]
public class Persons
{
public Persons ()
{
People = new List<Human>();
}
[XmlElement("Person")]
public List<Human> People
{ get; set; }
}
public class Human
{
public Human()
{
}
public Human(string name)
{
Name = name;
}
[XmlElement("Name")]
public string Name { get; set; }
}
Serializing your generic list using XmlElement means that it won't put the wrapper element around your list like XmlArray does or like the subclassing does. It also gives you the bonus option of adding attributes to the Persons class, which is where I got the idea from:
How do I add a attribute to a XmlArray element (XML Serialization)?
This is mine test code
using System.Collections.Generic;
using System.Xml.Serialization;
namespace TestLoadingMultiXml
{
[XmlRoot(ElementName=#"main")]
public class XmlMain
{
private XmlDataTest data;
[XmlElement(ElementName=#"datalist")]
public XmlDataTest Data
{
get { return data; }
set { data = value; }
} // public XmlDataTest Data
public XmlMain()
{
data = new XmlDataTest();
}
}
[XmlRoot(ElementName=#"xmldata")]
public class XmlDataTest
{
private List<DataDetails> listData;
[XmlElement(ElementName=#"listdata")]
public List<DataDetails> Data
{
get { return listData; }
set { listData = value; }
}
public XmlDataTest()
{
listData = new List<DataDetails>();
for (int i = 0; i < 10; i++)
{
DataDetails d = new DataDetails(string.Format("{0}", i));
listData.Add(d);
} // for (int i=0; i < 10; i++)
} // public XmlDataTest()
} // class XmlDataTest
[XmlRoot(ElementName=#"datadetail")]
public class DataDetails
{
private string name;
[XmlAttribute(AttributeName=#"name")]
public string Name
{
get
{
return name;
}
set { name = value; }
}
public DataDetails(string _value)
{
this.name = _value;
} // public DataDetails(string _value)
public DataDetails()
{
this.name = "";
} // public DataDetails()
} // public class DataDetails
}
and running program
using System;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace TestLoadingMultiXml
{
public partial class Form1 : Form
{
private XmlMain xt;
private string xname = #"x.xml";
public Form1()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
XmlSerializer x = new XmlSerializer(typeof(XmlMain));
FileStream fs = new FileStream(xname, FileMode.Create);
x.Serialize(fs, xt);
fs.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
xt = new XmlMain();
xname = Directory.GetCurrentDirectory() + #"\" + xname;
if (File.Exists(xname))
{
XmlSerializer x = new XmlSerializer(typeof(XmlMain));
FileStream fs = new FileStream(xname, FileMode.Open);
xt = (XmlMain)x.Deserialize(fs);
fs.Close();
} // if (File.Exists(xname))
}
}
}
Also this is the result
<?xml version="1.0"?>
<main xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<datalist>
<listdata name="0" />
<listdata name="1" />
<listdata name="2" />
<listdata name="3" />
<listdata name="4" />
<listdata name="5" />
<listdata name="6" />
<listdata name="7" />
<listdata name="8" />
<listdata name="9" />
</datalist>
</main>
Set the XmlRoot on Human to:
[XmlRoot("Person")]
Sidebar:
Persons should probably be People
There is another alternative. You can always implement IXmlSerializable in collections (and any other class or struct) to fully control how items are written or read. Many of you will know that already, it's not generally preferred of course as you may end-up writing out "boiler-plate" code by hand which should really be automatic logic specified with attributes.
For collections which must match a sensible schema it's justifiable. Because the framework has a hard limitation here and the existing item type's serialization code does not have to be duplicated when done properly; i.e. Do not re-write the item serialization in the collection code, just create/call a child XmlSerializer inside your ReadXml/WriteXml implementation.
Once consequence of using IXmlSerializable is it does not allow you to apply the XmlTypeAttribute (throws a runtime error telling you only XmlRootAttribute may be used). So instead apply the XmlSchemaProviderAttribute and return the same qualified name you would have put in the XmlTypeAttribute. The old GetSchema method should return null anyway as it was only a reserved method (according to MSDN), probably because they forgot to include the ability to specify a different namespace. Personally I use the same "GetSchema" method name in my XmlSchemaProviderAttribute so it appears as a complete override next to the legacy placeholder GetSchema method.
Of course, the best solution would be if Microsoft would allow us to apply the XmlArrayItemAttribute to collection/list classes and use that in the XmlSerializer. By default it uses the XML type element name in collections, which I feel is a bug because it should be the XML root name when specified or class name when not.
When I get time I'll come back and add an example. For now take a look at the MSDN documentation examples of IXmlSerializable and XmlSchemaProviderAttribute.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlschemaproviderattribute(v=vs.110).aspx
If you don't have access to the source for the Human class (in which case, setting XmlRoot is not possible), you can create an XmlElementAttribute, then add it to an XmlAttributeOverride and use that when creating an instance of your XmlSerializer. See this MSDN article for more details.
I know it's an old question but I ran into the same problem and none of the solutions seems to adresse the OP's question. So here is my solution (comments are in french if you wonder) :
#region Références
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
#endregion
namespace XmlSerializationTests
{
/// <summary>
/// Représente une liste qui peut être sérialisée en XML en tant que noeud racine.
/// </summary>
/// <typeparam name="T">Type des éléments de la liste.</typeparam>
public class XmlSerializableList<T>
: List<T>, IXmlSerializable
{
#region Variables
private static readonly XmlSerializer _ItemSerializer = new XmlSerializer(typeof(T));
private static readonly string _ItemName;
private string _RootName;
#endregion
#region Méthodes
/// <summary>
/// Initialisation statique
/// </summary>
static XmlSerializableList()
{
_ItemName = (typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? typeof(T).Name;
}
/// <summary>
/// Obtient le nom racine.
/// </summary>
protected virtual string RootName
{
get
{
if (string.IsNullOrWhiteSpace(_RootName)) _RootName = (GetType().GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? GetType().Name;
return _RootName;
}
}
/// <summary>
/// Obtient le nom des éléments.
/// </summary>
protected virtual string ItemName
{
get { return _ItemName; }
}
/// <summary>
/// Cette méthode est réservée et ne doit pas être utilisée.Lorsque vous implémentez l'interface IXmlSerializable, vous devez retourner la valeur null (Nothing dans Visual Basic) à partir cette méthode et, si la spécification d'un schéma personnalisé est requise, appliquez à la place <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> à la classe.
/// </summary>
/// <returns> <see cref="T:System.Xml.Schema.XmlSchema"/> qui décrit la représentation XML de l'objet qui est généré par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> et utilisé par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>.</returns>
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
/// Génère un objet à partir de sa représentation XML.
/// </summary>
/// <param name="reader"><see cref="T:System.Xml.XmlReader"/> source à partir de laquelle l'objet est désérialisé.</param>
public void ReadXml(XmlReader reader)
{
if (!reader.IsEmptyElement)
{
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
T item = (T) _ItemSerializer.Deserialize(reader);
Add(item);
}
reader.ReadEndElement();
}
else reader.ReadStartElement();
}
/// <summary>
/// Convertit un objet en sa représentation XML.
/// </summary>
/// <param name="writer"><see cref="T:System.Xml.XmlWriter"/> flux dans lequel l'objet est sérialisé.</param>
public void WriteXml(XmlWriter writer)
{
foreach (var i in this)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
_ItemSerializer.Serialize(writer, i, ns);
}
}
#endregion
}
}
And here a unit test class to demonstrate use and results :
#region Références
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endregion
namespace XmlSerializationTests
{
[TestClass]
public class XmlSerializableListTests
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Birth { get; set; }
}
[XmlRoot("color")]
public class ColorDefinition
{
[XmlElement("name")] public string Name { get; set; }
[XmlElement("r")] public int Red { get; set; }
[XmlElement("g")] public int Green { get; set; }
[XmlElement("b")] public int Blue { get; set; }
}
public class Persons : XmlSerializableList<Person>
{
}
[XmlRoot("colors")]
public class ColorList : XmlSerializableList<ColorDefinition>
{
}
private T ReadXml<T>(string text) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof (T));
using (StringReader sr = new StringReader(text))
{
return serializer.Deserialize(sr) as T;
}
}
private string WriteXml<T>(T data) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
serializer.Serialize(sw, data);
return sb.ToString();
}
}
[TestMethod]
public void ReadEmpty()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(0, lst.Count);
}
[TestMethod]
public void ReadEmpty2()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(0, lst.Count);
}
[TestMethod]
public void ReadSimpleItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
<int>0</int>
<int>52</int>
<int>79</int>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual(0, lst[0]);
Assert.AreEqual(52, lst[1]);
Assert.AreEqual(79, lst[2]);
}
[TestMethod]
public void ReadComplexItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</XmlSerializableListOfPerson>";
XmlSerializableList<Person> lst = ReadXml<XmlSerializableList<Person>>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("Linus", lst[0].FirstName);
Assert.AreEqual("Torvalds", lst[0].LastName);
Assert.AreEqual(1969, lst[0].Birth);
Assert.AreEqual("Bill", lst[1].FirstName);
Assert.AreEqual("Gates", lst[1].LastName);
Assert.AreEqual(1955, lst[1].Birth);
Assert.AreEqual("Steve", lst[2].FirstName);
Assert.AreEqual("Jobs", lst[2].LastName);
Assert.AreEqual(1955, lst[2].Birth);
}
[TestMethod]
public void ReadInheritedPersons()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</Persons>";
Persons lst = ReadXml<Persons>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("Linus", lst[0].FirstName);
Assert.AreEqual("Torvalds", lst[0].LastName);
Assert.AreEqual(1969, lst[0].Birth);
Assert.AreEqual("Bill", lst[1].FirstName);
Assert.AreEqual("Gates", lst[1].LastName);
Assert.AreEqual(1955, lst[1].Birth);
Assert.AreEqual("Steve", lst[2].FirstName);
Assert.AreEqual("Jobs", lst[2].LastName);
Assert.AreEqual(1955, lst[2].Birth);
}
[TestMethod]
public void ReadInheritedColors()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
<color>
<name>red</name>
<r>255</r>
<g>0</g>
<b>0</b>
</color>
<color>
<name>green</name>
<r>0</r>
<g>255</g>
<b>0</b>
</color>
<color>
<name>yellow</name>
<r>255</r>
<g>255</g>
<b>0</b>
</color>
</colors>";
ColorList lst = ReadXml<ColorList>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("red", lst[0].Name);
Assert.AreEqual(255, lst[0].Red);
Assert.AreEqual(0, lst[0].Green);
Assert.AreEqual(0, lst[0].Blue);
Assert.AreEqual("green", lst[1].Name);
Assert.AreEqual(0, lst[1].Red);
Assert.AreEqual(255, lst[1].Green);
Assert.AreEqual(0, lst[1].Blue);
Assert.AreEqual("yellow", lst[2].Name);
Assert.AreEqual(255, lst[2].Red);
Assert.AreEqual(255, lst[2].Green);
Assert.AreEqual(0, lst[2].Blue);
}
[TestMethod]
public void WriteEmpty()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
XmlSerializableList<int> lst = new XmlSerializableList<int>();
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteSimpleItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
<int>0</int>
<int>52</int>
<int>79</int>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = new XmlSerializableList<int>() {0, 52, 79};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteComplexItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</XmlSerializableListOfPerson>";
XmlSerializableList<Person> persons = new XmlSerializableList<Person>
{
new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
};
string result = WriteXml(persons);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteInheritedPersons()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</Persons>";
Persons lst = new Persons
{
new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteInheritedColors()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
<color>
<name>red</name>
<r>255</r>
<g>0</g>
<b>0</b>
</color>
<color>
<name>green</name>
<r>0</r>
<g>255</g>
<b>0</b>
</color>
<color>
<name>yellow</name>
<r>255</r>
<g>255</g>
<b>0</b>
</color>
</colors>";
ColorList lst = new ColorList
{
new ColorDefinition { Name = "red", Red = 255, Green = 0, Blue = 0 },
new ColorDefinition { Name = "green", Red = 0, Green = 255, Blue = 0 },
new ColorDefinition { Name = "yellow", Red = 255, Green = 255, Blue = 0 }
};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
}
}
I know it's old but for others that will come -
Before class Human, add [XmlType("Person")] instead of [XmlRoot("Person")]

Categories