I need to deserialize this xml (that I can't change):
<foo:a xmlns:foo="http://example.com">
<b>string</b>
</foo:a>
I made this class:
[DataContract(Name = "a", Namespace = "http://example.com")]
public class A
{
[DataMember(Name = "b", Order = 0)]
public string B;
}
And I did:
using (var streamObject = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
var ser = new DataContractSerializer(typeof(A));
return (A)ser.ReadObject(streamObject);
}
I get an object of class A, but the content of B is always null. I know it would work if the xml was using <foo:b>string</foo:b>, but that is not the case. What can I do to deserialize a DataMember with no namespace?
if you can do the pre-processing of xml before deserializing it, then try to do the following:
make namespace in your datacontract empty:
[DataContract(Name = "a", Namespace = "")]
public class A
{
[DataMember(Name = "b", Order = 0)]
public string B;
}
remove the namespace attribute from your xml before deserializing it
XDocument doc = XDocument.Parse("your xml here");
XElement root = doc.Root;
XAttribute attr = root.Attribute("xmlns");
if (attr != null)
{
attr.Remove();
}
and then deserialize the updated xml:
string newXml = doc.ToString();
A result = null;
DataContractSerializer serializer = new DataContractSerializer(typeof(A));
using (StringReader backing = new StringReader(newXml))
{
using (XmlTextReader reader = new XmlTextReader(backing))
{
result = (A) serializer.ReadObject(reader);
}
}
Related
We are facing a situation where the DataContract serializer (a WCF service) is not deserializing repeating XML elements without container element into a list; the list property ends up with zero elements.
The datacontracts we have:
[DataContract]
public class Requestor
{
[DataMember(IsRequired = true, Order = 0)]
public string RequestorRole;
[DataMember(IsRequired = true, Order = 1)]
public string RequestorGivenName;
[DataMember(IsRequired = true, Order = 2)]
public string RequestorSurName;
[DataMember(IsRequired = false, Order = 3)]
public List<RequestorIdentification> RequestorIdentification { get; set; }
}
[DataContract]
public class RequestorIdentification
{
[DataMember(IsRequired = true, Order = 0)]
public string IdentificationID;
[DataMember(IsRequired = true, Order = 1)]
public string IdentificationCategoryCode;
}
The XML we want to deserialize:
<Requestor xmlns="http://schemas.datacontract.org/2004/07/Serialization">
<RequestorRole>Physicians</RequestorRole>
<RequestorGivenName>Rich</RequestorGivenName>
<RequestorSurName>Smith</RequestorSurName>
<RequestorIdentification>
<IdentificationID>AB1234567</IdentificationID>
<IdentificationCategoryCode>DEA</IdentificationCategoryCode>
</RequestorIdentification>
<RequestorIdentification>
<IdentificationID>0123456789</IdentificationID>
<IdentificationCategoryCode>NPI</IdentificationCategoryCode>
</RequestorIdentification>
</Requestor>
How can we resolve the issue using the DataContract serializer?
So far we have found no way to make it work, and this page https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/collection-types-in-data-contracts was not of much help.
The same issue was resolved for the XmlSerializer in this question:
Deserializing into a List without a container element in XML
To deserialize the XML shown in the OP, one can use XmlElement. However, this requires the use of XmlSerializer instead of DataContractSerializer for serialization and deserialization.
According to this post
The DataContractSerializer does not support the programming model used
by the XmlSerializer and ASP.NET Web services. In particular, it does
not support attributes like XmlElementAttribute and
XmlAttributeAttribute. To enable support for this programming model,
WCF must be switched to use the XmlSerializer instead of the
DataContractSerializer.
The code below shows how one can deserialize the XML shown in the OP.
Create a Windows Forms App (.NET Framework)
Add Reference (System.Runtime.Serialization)
VS 2019
In VS menu, click Project
Select Add Reference...
Click Assemblies
Check System.Runtime.Serialization
Click OK
The following using directives are used in the code below:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
Create a class (name: RequestorRequestorIdentification.cs)
[DataContract(Name = "RequestorIdentification")]
public class RequestorRequestorIdentification
{
[DataMember]
public string IdentificationID { get; set; }
[DataMember]
public string IdentificationCategoryCode { get; set; }
}
Create a class (name: Requestor.cs)
[DataContract(Name ="Requestor")]
public class Requestor
{
[DataMember]
public string RequestorRole { get; set; }
[DataMember]
public string RequestorGivenName { get; set; }
[DataMember]
public string RequestorSurName { get; set; }
[XmlElement]
public List<RequestorRequestorIdentification> RequestorIdentification { get; set; } = new List<RequestorRequestorIdentification>();
}
Note: The use of XmlElement requires the use of XmlSerializer instead of DataContractSerializer for serialization and deserialization.
Create a class (name: HelperXml.cs)
public class HelperXml
{
public static T DeserializeXMLFileToObject<T>(string xmlFilename)
{
//usage: Requestor requestor = DeserializeXMLFileToObject<Requestor>(xmlFilename);
if (String.IsNullOrEmpty(xmlFilename))
throw new Exception("Error: xmlFilename is null or empty.");
return DeserializeXMLToObject<T>(File.ReadAllBytes(xmlFilename));
}
public static T DeserializeXMLToObject<T>(string xmlText)
{
//usage: Requestor requestor = DeserializeXMLToObject<Requestor>(xmlText);
//create new instance
using (System.IO.StringReader reader = new System.IO.StringReader(xmlText))
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T), "http://schemas.datacontract.org/2004/07/Serialization");
return (T)serializer.Deserialize(reader);
}
}
public static T DeserializeXMLToObject<T>(byte[] xmlBytes)
{
//usage: Requestor requestor = DeserializeXMLToObject<Requestor>(File.ReadAllBytes(xmlFilename));
//create new instance of MemoryStream
using (MemoryStream ms = new MemoryStream(xmlBytes))
{
using (XmlTextReader reader = new XmlTextReader(ms))
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T), "http://schemas.datacontract.org/2004/07/Serialization");
return (T)serializer.Deserialize(reader);
}
}
}
public static void SerializeObjectToXMLFile(object obj, string xmlFilename)
{
//Usage: Class1 myClass1 = new Class1();
//SerializeObjectToXMLFile(myClass1, xmlFilename);
if (String.IsNullOrEmpty(xmlFilename))
throw new Exception("Error: xmlFilename is null or empty.");
//Xml writer settings
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Encoding = Encoding.UTF8;
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(xmlFilename, settings))
{
//specify namespaces
System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
ns.Add(string.Empty, "http://schemas.datacontract.org/2004/07/Serialization");
//create new instance
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType(), "http://schemas.datacontract.org/2004/07/Serialization");
//write to XML file
serializer.Serialize(writer, obj, ns);
}
}
}
Usage (deserialize):
private Requestor _requestor = null;
...
//deserialize
//_requestor = HelperXml.DeserializeXMLFileToObject<Requestor>(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test.xml"));
//_requestor = HelperXml.DeserializeXMLToObject<Requestor>(System.Text.Encoding.UTF8.GetBytes(xmlText));
_requestor = HelperXml.DeserializeXMLToObject<Requestor>(xmlText);
Usage (serialize):
private Requestor _requestor = null;
...
//create new instance
_requestor = new Requestor();
_requestor.RequestorRole = "Physicians";
_requestor.RequestorGivenName = "Rich";
-requestor.RequestorSurName = "Smith";
_requestor.RequestorIdentification.Add(new RequestorRequestorIdentification() { IdentificationID = "AB1234567", IdentificationCategoryCode = "DEA" });
_requestor.RequestorIdentification.Add(new RequestorRequestorIdentification() { IdentificationID = "0123456789", IdentificationCategoryCode = "NPI" });
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.Filter = "XML File (*.xml)|*.xml";
sfd.FileName = "Test.xml";
if (sfd.ShowDialog() == DialogResult.OK)
{
HelperXml.SerializeObjectToXMLFile(_requestor, sfd.FileName);
Debug.WriteLine($"Info: Saved to '{sfd.FileName}'");
}
}
Additional Resources:
Types Supported by the Data Contract Serializer
DataContractAttribute Class
Using Data Contracts
Using the XmlSerializer Class
Collection Types in Data Contracts
Data Member Order
I have a file .xml inside multiple xml in one line.
How can I read this file and convert to object?
I tried with this code it works if there is only one.
Please help and thank you all
[XmlRoot(ElementName = "DepartmentMaster")]
public class DepartmentMaster
{
[XmlElement(ElementName = "DepartmentId")]
public int DepartmentId { get; set; }
[XmlElement(ElementName = "Name")]
public string Name { get; set; }
[XmlElement(ElementName = "Description")]
public string Description { get; set; }
[XmlElement(ElementName = "test")]
public int Test { get; set; }
}
//string xml = "<DepartmentMaster><DepartmentId>267854</DepartmentId><Name>Purchase</Name><Description>Purchase Department</Description><test>1</test></DepartmentMaster>";
string xml = "<DepartmentMaster><DepartmentId>267854</DepartmentId><Name>Purchase</Name><Description>Purchase Department</Description><test>1</test></DepartmentMaster><DepartmentMaster><DepartmentId>267855</DepartmentId><Name>Purchase5</Name><Description>Purchase Department5</Description><test>5</test></DepartmentMaster>";
using (TextReader reader = new StringReader(xml))
{
System.Xml.Serialization.XmlSerializer deserializer = new System.Xml.Serialization.XmlSerializer(typeof(DepartmentMaster));
var model = (DepartmentMaster)deserializer.Deserialize(reader);
}
image from the database
image from the database
Here it is two approaches below.
The first is using setting to accept XML data with multiple root elements (ConformanceLevel.Fragment).
private static IList<DepartmentMaster> DeserializeFragment(string xml)
{
var settings = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment
};
XmlReader reader = XmlReader.Create(new MemoryStream(Encoding.ASCII.GetBytes(xml)), settings);
var serializer = new XmlSerializer(typeof(DepartmentMaster));
var list = new List<DepartmentMaster>();
while (serializer.Deserialize(reader) is DepartmentMaster element)
{
list.Add(element);
}
return list;
}
And the second by adding a root element to deserialize a well-formed XML document.
public class DepartmentMasters
{
[XmlElement("DepartmentMaster")]
public List<DepartmentMaster> Items;
}
private static DepartmentMasters DeserializeWellFormedXML(string xml)
{
var text = #"<?xml version=""1.0""?><DepartmentMasters>" + xml + "</DepartmentMasters>";
var serializer = new XmlSerializer(typeof(DepartmentMasters));
return (DepartmentMasters)serializer.Deserialize(new StringReader(text));
}
I have a class like:
[XmlRoot(ElementName = "Root", Namespace = "https://NS.com")]
public class Root
{
[XmlElement(Namespace = "https://NS2.com")]
public Header header { set; get; }
public Body{ set; get; }
}
I serialize the obj to XML:
using (MemoryStream ms = new MemoryStream())
{
using (XmlWriter wr = XmlWriter.Create(ms))
{
serializer.Serialize(wr, obj, ns);
}
ms.Position = 0;
XmlDocument doc.Load(ms);
ms.Close();
}
I couldn't find any way to change the nameSpace of Root and Header to newNS, and newHeadNS. Is there a way to change namespace in the doc? Any help would be greatly appreciated
You can use the XmlAttributeOverrides to override that.
Initialize your serializer as such :
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Root), new XmlAttributes() { XmlRoot = new XmlRootAttribute() { Namespace = "https://NS.new" } });
var header = new XmlAttributes();
header.XmlElements.Add(new XmlElementAttribute() { Namespace = "https://NS.new/head" });
overrides.Add(typeof(Root), "Header", header);
var serializer = new XmlSerializer(typeof(Root), overrides);
And, the following to serialize :
serializer.Serialize(wr, obj);
I'm having trouble getting my top-level element to look exactly like this using the XmlSerializer (and C# attributes):
<rootObject xmlns="http://www.example.com/xmlschemas/nonStandardSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.com/xmlschemas/nonStandardSchema1.xsd">
<otherSerializedObjects />
</rootObject>
Currently, the closest I've gotten is this:
<rootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
d1p1:schemaLocation="http://www.example.com/xmlschemas/nonStandardSchema1.xsd"
xmlns:d1p1="xsi"
xmlns="http://www.example.com/xmlschemas/nonStandardSchema">
<otherSerializedObjects />
</rootObject>
C#
[XmlRoot("rootObject", Namespace = "http://www.example.com/xmlschemas/nonStandardSchema")]
public class RootObject
{
[XmlAttribute("schemaLocation", Namespace = "xsi")]
public string SchemaLocation { get; set; }
[XmlElement("Image")]
public Object[] OtherSerializedObjects { get; set; }
public RootObject()
{
OtherSerializedObjects = new Object[]{};
}
}
public class Program
{
static void Main(string[] args)
{
var rootObject = new RootObject
{
SchemaLocation = "http://www.example.com/xmlschemas/nonStandardSchema1.xsd",
OtherSerializedObjects = new object[]{}
};
var serializer = new XmlSerializer(typeof(RootObject));
var stringWriter = new StringWriter();
serializer.Serialize(stringWriter, rootObject);
Console.WriteLine(stringWriter.ToString());
}
}
Any ideas?
Firstly, the [XmlAttribute(Namespace=X)] attribute on your schemaLocation field/property needs to have the full namespace for the value of X, not the local namespace shortcut. Incidentally, this can be a property rather than a field. Using a field for this purpose wastes memory.
Secondly, to eliminate the standard xmlns:xsd="http://www.w3.org/2001/XMLSchema", use an XmlSerializer.Serialize overload where you pass in an XmlSerializerNamespaces with just the namespaces you want.
Thus:
[XmlRoot("rootObject", Namespace = "http://www.example.com/xmlschemas/nonStandardSchema")]
public class RootObject
{
public static XmlSerializerNamespaces GetAdditionalNamespaces()
{
XmlSerializerNamespaces xsNS = new XmlSerializerNamespaces();
xsNS.Add("", "http://www.example.com/xmlschemas/nonStandardSchema");
xsNS.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
return xsNS;
}
[XmlAttribute("schemaLocation", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string XSDSchemaLocation
{
get
{
return "http://www.example.com/xmlschemas/nonStandardSchema1.xsd";
}
set
{
// Do nothing - fake property.
}
}
[XmlElement("Image")]
public Object[] OtherSerializedObjects { get; set; }
public RootObject()
{
OtherSerializedObjects = new Object[]{};
}
}
And then use it like:
var rootObject = new RootObject
{
OtherSerializedObjects = new object[]{}
};
var serializer = new XmlSerializer(typeof(RootObject));
var stringWriter = new StringWriter();
var ns = RootObject.GetAdditionalNamespaces();
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(stringWriter, settings))
{
serializer.Serialize(xmlWriter, rootObject, ns);
}
Console.WriteLine(stringWriter.ToString());
Example fiddle.
Namespace needs to be the entire namespace, not the shortened version.
[XmlAttribute("schemaLocation", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
In my testing it automatically used xsi.
how I can check if an attribute or an element is missing in my xml, when I deserialize an Object from Xml?
XmlSerializer fill missing values with their defaults, but how I can know whether it is the default value or it's missing in the Xml?
I have to know this, because if I publish a new version of my program and have added values to my objects, I want to show a prompt with new (missing) values to the user. He have to know the circumstances.
[Serializable]
public class Dummy
{
public int MyInteger { get; set; }
public string MyString { get; set; }
public double MyDouble { get; set; }
public bool MyBool { get; set; }
public Dummy()
{
//Missing values in the xml would filled up with these values
MyInteger = default(int);
MyString = default(string);
MyDouble = default(double);
MyBool = default(bool);
}
}
class Program
{
static void Main(string[] args)
{
XmlSerializer serializer = new XmlSerializer(typeof(Dummy));
Dummy dummy = new Dummy(){ MyInteger = 1, MyBool = false, MyDouble = 3.4, MyString="dummy"};
StringBuilder sb = new StringBuilder();
using(StringWriter writer = new StringWriter(sb))
serializer.Serialize(writer, dummy);
/*sb contains:
* <?xml version="1.0" encoding="utf-16"?>
* <Dummy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
* <MyInteger>1</MyInteger>
* <MyString>dummy</MyString>
* <MyDouble>3.4</MyDouble>
* <MyBool>false</MyBool>
* </Dummy>
*/
//xml without MyDouble
string xml = #" <Dummy>
<MyInteger>1</MyInteger>
<MyString>dummy</MyString>
<MyBool>false</MyBool>
</Dummy>";
Dummy readDummy;
using (StringReader reader = new StringReader(xml))
readDummy = (Dummy)serializer.Deserialize(reader);
/*readDummy contains:
* MyInteger = 1,
* MyString = "dummy",
* MyDouble = 0,
* MyBool = false
*/
}
}
Update
Thank you Yeldar Kurmangaliyev for the Schema-Validation.
My current Problem is that the exception which was thrown by the Schema-Validator let me access only MyBool and not the missing value MyDouble.
The Exception-Message contains the name of the property MyDouble, but should I extract the property name from the Exception-Message? It feels very dirty.
Here is the updated code:
[Serializable]
public class Dummy
{
public int MyInteger { get; set; }
public string MyString { get; set; }
public double MyDouble { get; set; }
public bool MyBool { get; set; }
public Dummy()
{
//Missing values in the xml would filled up with these values
MyInteger = default(int);
MyString = default(string);
MyDouble = default(double);
MyBool = default(bool);
}
}
class Program
{
static void Main(string[] args)
{
XmlSerializer serializer = new XmlSerializer(typeof(Dummy));
string xml = #" <Dummy>
<MyInteger>1</MyInteger>
<MyString>dummy</MyString>
<MyBool>false</MyBool>
</Dummy>";
Dummy readDummy;
XmlReaderSettings settings = new XmlReaderSettings() { ValidationType = ValidationType.Schema };
settings.Schemas.Add(GetXmlSchemas(typeof(Dummy)).First());
settings.ValidationEventHandler += (s, e) =>
{
//I got an exception with the sender "MyBool". How I can reach the variable "MyDouble" which is missing?
};
using (StringReader reader = new StringReader(xml))
using (XmlReader xmlReader = XmlReader.Create(reader, settings))
readDummy = (Dummy)serializer.Deserialize(xmlReader);
}
public static XmlSchemas GetXmlSchemas(Type type)
{
var schemas = new XmlSchemas();
var exporter = new XmlSchemaExporter(schemas);
var mapping = new XmlReflectionImporter().ImportTypeMapping(type);
exporter.ExportTypeMapping(mapping);
return schemas;
}
}
You can use XmlReaderSettings class for initializing and using XmlReader over a StringReader. However, you will need an XSD schema.
That's a proper way to validate XML on deserializing.
Take a look at XmlReaderSettings. Maybe, you will find an easier way to do this :)
string xml = #" <Dummy>
<MyInteger>1</MyInteger>
<MyString>dummy</MyString>
<MyBool>false</MyBool>
</Dummy>";
Dummy readDummy;
XmlSchemaSet schemas = null; // here is your schema
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(schemas);
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += (s, e) =>
{
throw e.Exception; // Here you go
};
using (StringReader reader = new StringReader(xml))
using (XmlReader xmlReader = XmlReader.Create(reader, settings))
readDummy = (Dummy)serializer.Deserialize(xmlReader);