Reputation: 35
I'm calling a generic endpoint /api/foo
(third party, no control over it) which returns an XML describing foo
. You can pass whatever entity name as foo
and it will return its data.
Below are some example of the responses.
When calling api/foo1
:
<foo1>
<name>SomeValue</name>
<id>101</id>
</foo1>
When calling api/foo2
:
<foo2>
<name>SomeOtherValue</name>
<id>205</id>
</foo2>
Basically the XML opens with the foo
parameter requested in the path. Fields inside (name, id) are fixed. The foo
entity names can be user defined, so there's not a fixed known a-priori list I can work with.
I'm trying to deserialize the XML using this DataContractSerializer
and this DataContract
:
[DataContract(Name = "foo1", Namespace = "")]
public class Foo
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "id")]
public string Id { get; set; }
}
Of course this works fine for foo1
, but doesn't work for any other entity name.
Is it possible to somehow parameterize the DataContract
Name
at runtime? I'm building the serializer myself.
Minimal reproducible example here. The first assertion is working fine, second one is failing.
[DataContract(Name = "foo1", Namespace = "")]
public class Foo
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "id")]
public string Id { get; set; }
}
[Fact]
public void TestDeserialization()
{
var foo1 = "<foo1>\n\t<name>SomeValue</name>\n\t<id>101</id>\n</foo1>";
var foo2 = "<foo2>\n\t<name>SomeOtherValue</name>\n\t<id>205</id>\n</foo2>";
var serializer = new DataContractSerializer(typeof(Foo));
var objFoo1 = (Foo)serializer.ReadObject(XmlReader.Create(new StringReader(foo1)));
var objFoo2 = (Foo)serializer.ReadObject(XmlReader.Create(new StringReader(foo2)));
bjFoo1.Name.Should().Be("SomeValue");
bjFoo2.Name.Should().Be("SomeOtherValue");
}
Upvotes: -1
Views: 67
Reputation: 117275
You can use the DataContractSerializer (Type type, string rootName, string rootNamespace)
constructor to specify the root name and namespace in runtime:
var serializer2 = new DataContractSerializer(typeof(Foo), "foo2", "");
var objFoo2 = (Foo)serializer2.ReadObject(XmlReader.Create(new StringReader(foo2)));
objFoo2.Name.Should().Be("SomeOtherValue"); // Passes
And if, for some reason, you don't know the root name and namespace, you could get them from your XmlReader
like so:
var foo3xml = "<unknownRoot>\n\t<name>SomeOtherValue</name>\n\t<id>205</id>\n</unknownRoot>";
using (var textReader = new StringReader(foo3xml))
using (var xmlReader = XmlReader.Create(textReader))
{
if (xmlReader.MoveToContent() != XmlNodeType.Element) // Advance to the root mode
throw new XmlException("No root node found");
var serializer3 = new DataContractSerializer(typeof(Foo), xmlReader.LocalName, xmlReader.NamespaceURI);
var objFoo3 = (Foo)serializer3.ReadObject(xmlReader);
objFoo3.Name.Should().Be("SomeOtherValue"); // Passes
objFoo3.Id.Should().Be("205");
}
(That being said, not knowing the root node name in advance would be strange, it would indicate you don't know the schema of the XML being returned. As such, this is not recommended for use in typical situations.)
Finally, be aware that the data contract serializer is order-sensitive and expects the data members to be in alphabetical order by default. Since your <id>
element appears after <name>
you must set DataMemberAttribute.Order
:
[DataContract(Name = "foo1", Namespace = "")]
public class Foo
{
[DataMember(Name = "name", Order = 1)]
public string Name { get; set; }
[DataMember(Name = "id", Order = 2)]
public string Id { get; set; }
}
Demo fiddle here.
Upvotes: 1
Reputation: 41
In case that you defined the endpoint, why not change it so it always returnes a <foo>
and add a third parameter called <type>
.
So it looks like this for foo1:
<foo>
<type>foo1</type>
<name>SomeValue</name>
<id>101</id>
</foo>
And like this for foo2:
<foo>
<type>foo2</type>
<name>SomeValue</name>
<id>101</id>
</foo>
And in your data class you just need to change it to this:
[DataContract(Name = "foo", Namespace = "")]
public class Foo
{
[DataMember(Name = "type")]
public string Type{ get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "id")]
public string Id { get; set; }
}
Upvotes: -1