I am having problems deserializing XML to an object with a list. The deserialize runs without error but the Resources list is empty, and I know there is one resource returned in the XML. It seems like it is just not being deserialised properly.
I am using the following code to deserialize
var ser = new XmlSerializer(typeof(SearchResult));
var test = result.Content.ReadAsStringAsync();
var t = (SearchResult)ser.Deserialize(result.Content.ReadAsStreamAsync().Result);
The variable "test" on line 2 of the code above is equal to
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ns3:searchResult total="1" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns3="ers.ise.cisco.com">
<resources>
<resource id="76a4b0f2-64e2-11e6-9f15-00505688a404" name="5555884552">
<link rel="self" href="https://servername123:9060/ers/config/guestuser/76a4b0f2-64e2-11e6-9f15-00505688a404" type="application/xml"/>
</resource>
</resources>
</ns3:searchResult>
So I would expect to get one ExistingWifiAccountDto in the Resources list. But I don't. It is empty. What am I doing wrong?
The classes for object mapping are below
[XmlRoot(ElementName = "searchResult", Namespace = "ers.ise.cisco.com")]
public class SearchResult
{
public SearchResult()
{
Resources = new List<ExistingWifiAccountDto>();
}
[XmlArray("resources")]
[XmlArrayItem("resource", typeof(ExistingWifiAccountDto))]
public List<ExistingWifiAccountDto> Resources { get; set; }
}
public class ExistingWifiAccountDto
{
public ExistingWifiAccountDto()
{
}
[XmlAttribute("id")]
public string Id { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("link")]
public LinkDto Link { get; set; }
}
public class LinkDto
{
public LinkDto()
{
}
[XmlAttribute("rel")]
public string Rel { get; set; }
[XmlAttribute("href")]
public string Href { get; set; }
[XmlAttribute("type")]
public string Type { get; set; }
}
You'll have to set the namespace to an empty string for the "resources" array since it is not inherited in you situation. This should then flow down to child elements.
Try changing
[XmlArray("resources")]
to
[XmlArray("resources", Namespace = "")]
This will cause it to deserialize correctly, alternatively, you could also set each node from "resources" down with the Form attribute:
Form = XmlSchemaForm.Unqualified
Cheers
Related
I'm doing a request to server, which returns the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<result><category>...
I am using the following code to deserialize:
private ListCategories DeserializeXML()
{
XmlSerializer xml = new XmlSerializer(typeof(ListCategories));
using (FileStream fs = new FileStream("CategoriesListXML.xml", FileMode.OpenOrCreate))
{
return (ListCategories)xml.Deserialize(fs);
}
}
And i have the following for my class Categories
[Serializable]
public class ListCategories
{
public List<Categories> CategoriesList { get; set; } = new List<Categories>();
}
[Serializable]
public class Categories
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public string image { get; set; }
public Categories() { }
public Categories(int id, int parent_id, string name, string image)
{
this.id = id;
this.parent_id = parent_id;
this.name = name;
this.image = image;
}
}
But when I deserialize the xml document I get a problem in this line:
return (ListCategories)xml.Deserialize(fs);
InvalidOperationException: <result xmlns=''> was not expected.
So can someone explain to me why the error is happening? As well as a possible solution?
Nothing in your defined model is result, so it makes sense that it is confused about what to do with <result>; the root element of ListCategories as defined is: <ListCategories>. If that isn't what you expect, you can use attributes to tell it what it should be, for example [XmlRoot("result")] on the class ListCategories. But: we'd need to see what you expect to advise on the exact attributes.
If I had to guess:
[XmlRoot("result")]
public class ListCategories
{
[XmlElement("category")]
public List<Categories> CategoriesList { get; set; } = new List<Categories>();
}
Note you can lose the [Serializable]. That has nothing to do with xml serialization. You can also probably lose the set on CategoriesList.
I started with the following code:
public class ChangeRestartProcessing
{
[XmlElement("ID")]
public long TransportId { get; set; }
[XmlElement("FI")]
public Information FinalInformation { get; set; }
}
... and while serialising, everything worked fine: FI was visible in the XML serialisation result.
Now, I've made following modification:
public class ChangeRestartProcessing
{
[XmlElement("ID")]
public long TransportId { get; set; }
[XmlElement("FI")]
public Information FinalInformation { get; set; }
[XmlElement("DI")]
public Information NormalInformation { get; set; }
}
The idea is to see the FI when that object exists, and to see DI when that object exists (it never happens that both are present).
However, now I don't see any FI or DI tag in my XML serialisation result.
Is that normal and what can I do in order to make the mentioned tags visible? Do I need to create a separate class for both cases or is there another approach?
Works fine here...
using System;
using System.Xml.Serialization;
var serializer = new XmlSerializer(typeof(ChangeRestartProcessing));
serializer.Serialize(Console.Out, // has ID+FI only
new ChangeRestartProcessing
{
FinalInformation = new() { Id = 42 },
});
Console.WriteLine();
serializer.Serialize(Console.Out, // has ID+DI only
new ChangeRestartProcessing
{
NormalInformation = new() { Id = 42 },
});
public class ChangeRestartProcessing
{
[XmlElement("ID")]
public long TransportId { get; set; }
[XmlElement("FI")]
public Information FinalInformation { get; set; }
[XmlElement("DI")]
public Information NormalInformation { get; set; }
}
public class Information
{
[XmlAttribute]
public int Id { get; set; }
}
output:
<?xml version="1.0" encoding="Codepage - 850"?>
<ChangeRestartProcessing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>0</ID>
<FI Id="42" />
</ChangeRestartProcessing>
<?xml version="1.0" encoding="Codepage - 850"?>
<ChangeRestartProcessing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>0</ID>
<DI Id="42" />
</ChangeRestartProcessing>
I have the following test XML string:
<?xml version="1.0" encoding="UTF-8"?>
<test id="myid">
<b>b1</b>
<a>a2</a>
<a>a1</a>
<b>b2</b>
</test>
which I deserialize using this class:
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement(ElementName = "a")]
public List<string> A { get; set; }
[XmlElement(ElementName = "b")]
public List<string> B { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
If I'm now going to serialize the object the result will be:
<?xml version="1.0" encoding="utf-16"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="myid">
<a>a2</a>
<a>a1</a>
<b>b1</b>
<b>b2</b>
</test>
Is there a way to keep the initial sort order?
I guess I can't use [XmlElementAttribute(Order = x)] cause the order shouldn't be hardcoded but identically with the initial xml.
I thought about adding an order property to my lists like that
[XmlRoot(ElementName="a")]
public class A
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="b")]
public class B
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="test")]
public class Test
{
[XmlElement(ElementName="a")]
public List<A> A { get; set; }
[XmlElement(ElementName="b")]
public List<B> B { get; set; }
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
}
but I don't know how to order by them when serializing.
You can do this with XmlSerializer by using a single collection to capture both the <a> and <b> elements and applying the [XmlElement(Name, Type = typeof(...))] attribute to it multiple times, once for each desired element name. Because you are using a single collection to deserialize both elements, the order is automatically preserved. However, to make this work, XmlSerializer must be able to determine the correct element name when re-serializing. There are two approaches to accomplish this, as documented in Choice Element Binding Support:
If the collection contains polymorphic items, the element name can be mapped to the concrete item type by using the [XmlElementAttribute(String, Type)] constructor. For instance, if you have a sequence of elements that might be strings or integers like so:
<Things>
<string>Hello</string>
<int>999</int>
</Things>
This can be bound to a collection as follows:
public class Things
{
[XmlElement(Type = typeof(string)),
XmlElement(Type = typeof(int))]
public List<object> StringsAndInts { get; set; }
}
If the collection contains only a single type of item, the element name can be encoded in an associated array of enum values, where the enum names correspond to the element names and the array itself is identified via the [XmlChoiceIdentifierAttribute] attribute.
For details, see the documentation examples.
I find option #1 easier to work with than option #2. Using this approach, the following model will deserialize and re-serialize your XML while successfully preserving the order of the <a> and <b> elements:
public abstract class StringElementBase
{
[XmlText]
public string Text { get; set; }
public static implicit operator string(StringElementBase element)
{
return element == null ? null : element.Text;
}
}
public sealed class A : StringElementBase
{
}
public sealed class B : StringElementBase
{
}
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement("a", Type = typeof(A))]
[XmlElement("b", Type = typeof(B))]
public List<StringElementBase> Items { get; } = new List<StringElementBase>();
[XmlIgnore]
// For convenience, enumerate through the string values of the items.
public IEnumerable<string> ItemValues { get { return Items.Select(i => (string)i); } }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
Working .Net fiddle.
For more examples of using [XmlChoiceIdentifier] to deserialize a sequence of elements with different names to c# objects of the same type, see for instance here or here.
No, basically; XmlSerializer doesn't support that. If you want to use that option you'd need to write it manually with XDocument or XmlDocument or XmlReader.
A legacy application transmits data as xml elements.
<frame>
<reply>
<id>value_id</id>
<resultCode>value</resultCode>
<readSampleIDs>
<returnValue>
<Sample>
<SampleID> value_SampleID </SampleID>
<SamplePC> value_SamplePC </SamplePC>
<antennaName> value_antennaName </antennaName>
<channel> value_channel </channel>
<power> value_power </power>
<polarization> value_polarization </polarization>
<inventoried> value_inventoried </inventoried>
</Sample>
…
<Sample>
…
</Sample>
</returnValue>
</readSampleIDs>
</reply>
Currently the information is extracted parsing the string word by word.
I think that xml element can be deserialized into an object directly with XmlSerializer but I have some doubts on how to do it.
The element frame contains only one reply. Do I really need two different classes ?
Inside returnValue there can be zero or more Sample. In my class what is the correct type, List<Sample> or Sample[] ? Is there a real difference between the two options during deserialization ?
Most fields inside Sample are optional. How do I model this ?
When an object is serialized with XmlSerializer information about xml version and additional attributes on root element are automatically added such as:
<?xml version="1.0" encoding="utf-8"?>
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.cpandl.com">
They are not present in my example code so I'm afraid deserialization can complain about it and possibly fail.
Thanks
Filippo
If your xml contains a namespace (like in your example) then you can use these classes:
[XmlRoot(ElementName = "Sample")]
public class Sample
{
[XmlElement(ElementName = "SampleID")]
public string SampleID { get; set; }
[XmlElement(ElementName = "SamplePC")]
public string SamplePC { get; set; }
[XmlElement(ElementName = "antennaName")]
public string AntennaName { get; set; }
[XmlElement(ElementName = "channel")]
public string Channel { get; set; }
[XmlElement(ElementName = "power")]
public string Power { get; set; }
[XmlElement(ElementName = "polarization")]
public string Polarization { get; set; }
[XmlElement(ElementName = "inventoried")]
public string Inventoried { get; set; }
}
[XmlRoot(ElementName = "readSampleIDs")]
public class ReadSampleIDs
{
[XmlArray(ElementName = "returnValue")]
[XmlArrayItem(ElementName = "Sample")]
public List<Sample> Sample { get; set; }
}
[XmlRoot(ElementName = "reply", Namespace = "http://www.cpandl.com")]
public class Reply
{
[XmlElement(ElementName = "id")]
public string Id { get; set; }
[XmlElement(ElementName = "resultCode")]
public string ResultCode { get; set; }
[XmlElement(ElementName = "readSampleIDs")]
public ReadSampleIDs ReadSampleIDs { get; set; }
}
If you are only interested in node reply and this is the only one. You can deserialize only this node:
XNamespace loNameSpace = "http://www.cpandl.com";
XDocument loDoc = XDocument.Parse(Properties.Settings.Default.TransmitsData);
var loReplyElement = loDoc.Element(loNameSpace.GetName("PurchaseOrder"))
.Element(loNameSpace.GetName("frame"))
.Element(loNameSpace.GetName("reply"));
using (var loReader = loReplyElement.CreateReader())
{
var loSerializer = new XmlSerializer(typeof(Reply));
var loReply = (Reply)loSerializer.Deserialize(loReader);
Console.WriteLine(loReply.Id);
}
My XML:
<result>
<document version="2.1.0">
<response type="currency">
<currency>
<code>AMD</code>
<price>85.1366</price>
</currency>
</response>
<response type="currency">
<currency>
<code>AUD</code>
<price>31.1207</price>
</currency>
</response>
</document>
</result>
My Class:
public class CurrencyData
{
public string Code { get; set; }
public string Price { get; set; }
}
My deserializer calling:
RestClient.ExecuteAsync<List<CurrencyData>>...
If i renamed class CurrencyData to Currency then all will been done right. But I want to keep this class name.
Ok, I think I got it,
You can try RestClient.ExecuteAsync<Result>()
[XmlRoot("result")]
public class Result
{
[XmlElement("document")]
public Document Document { get; set; }
}
public class Document
{
[XmlElement("response")]
public Response[] Responses { get; set; }
[XmlAttribute("version")]
public string Version { get; set; }
}
public class Response
{
[XmlElement("currency")]
public CurrencyData Currency { get; set; }
[XmlAttribute("type")]
public string Type { get; set; }
}
public class CurrencyData
{
[XmlElement("code")]
public string Code { get; set; }
[XmlElement("price")]
public decimal Price { get; set; }
}
I had to add a few XmlElement attribute to override the casing without having to name classes and properties in lowercase. but you can drop them if you can change the xml to match the casing
Answer of kay.one is perfect! It works with a any remarks:
public List<Response> Responses { get; set; }
works
public Response[] Responses { get; set; }
don`t works
And
[XmlElement("AnyValue")]
it is from System.Xml.Serialization namespace and don`t work.
Feel free to just delete them.
In this example annotation attributes and properties has same names and serializer understands.
But right annotation attributes are from RestSharp.Deserializers namespace
[DeserializeAs(Name="AnyXmlValue")]
public string AnyModelValue { get; set; }
How to manage deserialization of RestSharp
Then change the xml tag to CurrencyData. Here is the documentation about the xml deserializer: https://github.com/restsharp/RestSharp/wiki/Deserialization
I'm not sure why Kay.one's answer is accepted, it doesn't answer the question.
Per my comment, the default RestSharp deserializer doesn't check for the DeserializeAs attribute when deserializing a list. I'm not sure if that's intentional or a mistake as the author doesn't seem to be very available.
Anyways it's a simple fix.
private object HandleListDerivative(object x, XElement root, string propName, Type type)
{
Type t;
if (type.IsGenericType)
{
t = type.GetGenericArguments()[0];
}
else
{
t = type.BaseType.GetGenericArguments()[0];
}
var list = (IList)Activator.CreateInstance(type);
var elements = root.Descendants(t.Name.AsNamespaced(Namespace));
var name = t.Name;
//add the following
var attribute = t.GetAttribute<DeserializeAsAttribute>();
if (attribute != null)
name = attribute.Name;