Reputation: 33
I've connected to a web service and downloading XML which I'm trying to deserialize. However, the XML has a top level element of a different type than the child element that I'm trying to serialize to.
<ServiceResponse xmlns="namespace1">
<ServiceResult xmlns:i="namespace2">
...lots of elements
</ServiceResult>
</ServiceResponse>
I've written the following code:
public MyData ConvertXmlToMyData(string filepath)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "ServiceResponse";
xRoot.Namespace = @"namespace1";
xRoot.IsNullable = true;
XmlSerializer reader = new XmlSerializer(typeof(MyData), xRoot);
StreamReader file = new StreamReader(filepath);
MyData result = (MyData)reader.Deserialize(file);
file.Close();
return result;
}
The code executes with no exception, but all the underlying elements are null. I know this is because ServiceResponse
is not the same as ServiceResult
, but I don't know how to specify that I want the deserialization to happen on the child element, not the whole object (there are no other elements, just a single child of type ServiceResult
).
The only solutions I've seen state that I should edit the declaration of the types, but in my situation I'm getting them from a web service so I can't do that.
Does anyone know what I should be doing instead?
Edit: added details of the MyData annotations from the service reference
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="MyData", Namespace="namespace2")]
[System.SerializableAttribute()]
public partial class MyData: object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
Edit 2:
Changed code to:
public MyData ConvertXmlToMyData(string filepath)
{
DataContractSerializer reader = new DataContractSerializer(typeof(MyData));
StreamReader file = new StreamReader(filepath);
file.BaseStream.Position = 0;
MyData result = (MyData)reader.ReadObject(file.BaseStream); <- error thrown here
file.Close();
return result;
}
Now seeing error:
Error in line 1 position 73. Expecting element 'MyData' from namespace 'namespace1'.. Encountered 'Element' with name 'ServiceResponse', namespace 'namespace1'.
Upvotes: 0
Views: 78
Reputation: 22839
Let's suppose your xml file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<ServiceResponse xmlns="namespace1">
<ServiceResult xmlns:i="namespace2">
<desc>1</desc>
</ServiceResult>
</ServiceResponse>
I've just added an xml
and a desc
to your sample.
Then in order to skip the top-level entity we need to semi-parse the xml. One way to do this:
Load
the FileStream
into an XDocument
//Step #1
var xml = File.OpenRead("sample.xml");
var semiParsedData = XDocument.Load(xml);
//Step #2
const string topNamespace = "namespace1", firstNode = "ServiceResponse";
const StringComparison comparison = StringComparison.InvariantCultureIgnoreCase;
var topLevelEntity = semiParsedData.Descendants().FirstOrDefault(element =>
string.Equals(element.Name.Namespace.NamespaceName, topNamespace, comparison)
&& string.Equals(element.Name.LocalName, firstNode, comparison));
//Step #3
const string secondNode = "ServiceResult";
var secondLevelEntity = topLevelEntity?.Descendants().FirstOrDefault(element =>
string.Equals(element.Name.Namespace.NamespaceName, topNamespace, comparison)
&& string.Equals(element.Name.LocalName, secondNode, comparison));
if (secondLevelEntity == null)
return;
Hopefully there is a method called CreateReader
, which is defined on the XNode
class.
secondLevelEntity
is a XElement
instance.XElement
is inherited from XContainer
.XContainer
is inherited from XNode
.So, we can call the CreateReader
on the secondLevelEntity
:
using var resultReader = secondLevelEntity.CreateReader();
The resultReader
is a XmlReader
instance and hopefully the DataContractSerializer
's ReadObject
has an overload which accepts a XmlReader
:
var deserializer = new DataContractSerializer(typeof(MyData));
var myData = (MyData)deserializer.ReadObject(resultReader);
Console.WriteLine(myData.Description);
And finally the data model should look like this:
[DataContract(Name = "ServiceResult", Namespace = "namespace1")]
public class MyData
{
[DataMember(Name = "desc")]
public string Description { get; set; }
}
Upvotes: 1