I have XML that is returned by a 3rd party web service in the following form:
<object>
<list name="subscriptions">
<object>
<string name="id">something</string>
<string name="title">something else</string>
<list name="categories" />
<number name="timestamp">1252707770116</number>
</object>
...more 'object'...
</list>
</object>
I'm having a lot of issues trying to deserialize this data to an object. I wasn't able to generate a schema using xsd.exe, so I generated the following classes by hand to try and fit this data:
[XmlRoot("object")]
public class ListOfSubscriptions
{
[XmlElement("list")]
public Subscription[] items;
}
[XmlRoot("object")]
public class Subscription
{
[XmlAttribute()]
public string id;
[XmlAttribute()]
public string title;
[XmlAttribute()]
public string[] categories;
[XmlAttribute()]
public string timestamp;
}
I'm trying to deserialize this with the following code:
XmlSerializer s = new XmlSerializer(typeof(ListOfSubscriptions));
StreamReader r = new StreamReader("out.xml");
ListOfSubscriptions listSubscribe = (ListOfSubscriptions)s.Deserialize(r);
r.Close();
However, when it finishes, listSubscribe has one member and all its fields are null.
How should I be creating my template for deserializing?
Thanks
Update - 2010/01/28
Thanks to everybody's comments I've revised my classes to the following:
[XmlRoot("object")]
public class ListOfSubscriptions
{
[XmlElement("list")]
public SubscriptionList[] items;
}
[XmlRoot("list")]
public class SubscriptionList
{
[XmlElement("object")]
public Subscription[] items;
}
[XmlRoot("object")]
public class Subscription
{
[XmlElement("string")]
public string id;
[XmlElement("string")]
public string title;
[XmlElement("list")]
public string[] categories;
[XmlElement("number")]
public string timestamp;
}
If I comment out the [XmlElement(...)] lines in Subscription, and run, I get that listSubscribe has one SubscriptionList item which has the correct number of Subscriptions, however, all the elements of the subscriptions are null.
If I uncomment the XmlElement lines, I get an error reflecting a Subscription. I imagine its getting confused because there are multiple elements with the same name.
How do I tie the attribute name to the class member?
You're on the right track. However, you are only defining two classes. There are actually three classes to define:
The single root object, with an XML name of "object". This will have only one member:
The list of objects (with an XML name of "list"). This will have one member, an array of:
Subscriptions, with an XML name of "object".
Another problem is that you are defining the Subscription fields as attributes. They aren't attributes, they're elements.
You're never going to get anywhere with XML like this:
<string name="id">something</string>
That's just created by someone who doesn't know XML. The equivalent:
<id>something</id>
would be easy to deserialize.
The only way I can think of for you to deserialize that is by implementing the IXmlSerializable interface on your class(es).
You will want to change out XmlArry and XMLArrayItem.
Here is an example. It should look something like this:
[XmlArray("FullNames")]
[XmlArrayItem("Name")]
public string[] Names{get;set;}
will give you
<FullNames>
<Name>Michael Jackson</Name>
<Name>Paris Hilton</Name>
</FullNames>
I believe you can use XSD.exe .Net framework utility to generate a class that can be used a memory representation of your XML document.
Related
I'm writing a C# library that, as one of its functions, needs to be able to accept XML of the following forms from a web service and deserialize them.
Form 1:
<results>
<sample>
<status>status message</status>
<name>sample name</name>
<igsn>unique identifier for this sample</igsn>
</sample>
</results>
Form 2:
<results>
<sample name="sample name">
<valid code="InvalidSample">no</valid>
<status>Not Saved</status>
<error>error message</error>
</sample>
</results>
Here's my class that I'm deserializing to:
namespace MyNamespace
{
[XmlRoot(ElementName = "results")]
public class SampleSubmissionResponse
{
[XmlElement("sample")]
public List<SampleSubmissionSampleResultRecord> SampleList { get; set; }
...
}
public class SampleSubmissionSampleResultRecord
{
...
/* RELEVANT PROPERTY RIGHT HERE */
[XmlAttribute(Attribute = "name")]
[XmlElement(ElementName = "name")]
public string Name { get; set; }
...
}
public class SampleSubmissionValidRecord
{
...
}
}
The problem is that in one XML sample, the name attribute of the Sample element is an element, and in the other it's an attribute. If I decorate the property of my class with both XmlAttribute and XmlElement, I get an exception thrown when creating an instance of XmlSerializer.
I've been googling for a good while now, and I can't find any docs that deal with this situation. I assume, but don't know for sure, that this is because when creating an XML schema, you're not supposed to use the same name for an attribute and a child element of the same element.
So, what do I do here?
One solution might be to have two totally separate models for the different types. That would probably work, but doesn't seem very elegant.
Another option might be to implement IXmlSerializable and write some elaborate code to handle this in the deserialize method. That would be an awfully verbose solution to a simple problem.
Third option I'm hoping for: some way of applying both XmlAttribute and XmlElement to the same property, or an equivalent "either-or" attribute.
Fourth option: Change the web service the XML comes from to use one form consistently. Unfortunately, the folks who own it may not be willing to do this.
Specify only one attribute to Name property. This will correctly parse out the first xml form.
public class SampleSubmissionSampleResultRecord
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
}
To parse the second xml form, subscribe the XmlSerializer to the UnknownAttribute event.
var xs = new XmlSerializer(typeof(SampleSubmissionResponse));
xs.UnknownAttribute += Xs_UnknownAttribute;
In the event handler, we get the desired value.
private void Xs_UnknownAttribute(object sender, XmlAttributeEventArgs e)
{
var record = (SampleSubmissionSampleResultRecord)e.ObjectBeingDeserialized;
record.Name = e.Attr.Value;
}
I'm using the built in XML deserialization (not because it was my choice, but legacy code) to deserialize xml to a strong typed object.
NOTE: I have no control over the xml, it is an external api
The problem is an xml node has been extended to include a child node of the same name and it's breaking the serialization.
For example, the xml as follows:
<people>
<person>
<id>1234</id>
<person>
<name>This is my name</name>
<person>
</person>
</people>
With the following objects
[XmlType("person")]
public class Person {
[XmlElement("id")]
public int Id { get; set; }
[XmlElement("person")]
public PersonTitle Title{ get; set; }
}
[XmlType("person")]
pulic class PersonTitle
{
[XmlElement("name")]
public string Name { get; set; }
}
This is throwing an error when calling (T)xmlserializer.Deserialize(stream) due to the duplicate names even though the xml is valid. Personally I would not have gone to the trouble to replicate the xml layout in objects just to automatically deserialize it when manually deserializing is easier to maintain (especially when it's never serialized by .net in the first place).
However, I'd like to know if there's a way I can get around this even if it means flattenting the child object out.
I know this doesn't work, but as example:
[XmlType("person")]
public class Person {
[XmlElement("id")]
public int Id { get; set; }
[XmlElement("person/name")]
public string Title{ get; set; }
}
Any help is appreciated.
The easiest method might be to run it through an XSLT transform before deserializing it- match the person/person/name elements and output just the person/name part. Then deserialize the result.
Here's a SO post on applying XSLT within C#: How to apply an XSLT Stylesheet in C#
And here is one on using XSLT to replace elements: http://cvalcarcel.wordpress.com/2008/09/06/replacing-arbitrary-xml-located-within-an-xml-document-using-xslt/
In a worst case scenario you could write the class however you like (don't compromise due to serialization) and then implement IXmlSerializable. Implement ReadXml, throw NotImplementedException for WriteXml if you like.
I have a fairly simple DAL assembly that consists of an SalesEnquiry Class which contains a List<T> of another Vehicle class.
We'll be receiving XML files by email that I'm wanting to use to populate instances of my SalesEnquiry class, so I'm trying to use de-serialization.
I've added XMLRoot/XMLElement/XMLIgnore attributes to both classes as I think is appropriate. However, when I try de-serializing, the parent SalesEnquiry object is populated but no child Vehicle objects.
I understand that de-serializing List<T> can be tricky, but I'm not sure why, how to avoid problems, or even if this is why I'm struggling.
While debugging, I've successfully serialized a Vehicle object on it's own, so I'm assuming that I'm heading in the right direction, but when I de-serialize the SalesEnquiry XML (which contains one or more child Vehicles), the List<Vehicle> isn't populated.
Where am I going wrong?
Update:
In a test project, I serialized an SalesEnquiry containing two vehicles and save to a file. I then loaded the file up an de-serialized it back into a different SalesEnquiry object. It worked!
So what was the difference? The vehicles were recorded as follows:
<?xml version="1.0" encoding="utf-8"?>
<enquiry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<enquiry_no>100001</enquiry_no>
<vehicles>
<Vehicle>
<vehicle_type>Car</vehicle_type>
<vehicle_make>Ford</vehicle_make>
<vehicle_model>C-Max</vehicle_model>
...
The thing to note is that Vehicle has a an initial capital, whereas my incoming XML doesn't. In my Vehicle class, I gave the class an [XmlRoot("vehicle")] attribute, which I though would make the link, but clearly it doesn't. This makes sense, I guess, because although Vehicle is a class in it's own right, it's merely an array item within a List inside my SalesEnquiry.
In which case, the question is - How do I annotate the Vehicle class such that I can map the incoming XML elements (<vehicle>) to my list items (Vehicle)? [XmlArrayItem] (or [XmlElement] for that matter) are 'not valid on this declaration type'.
In this example, I can request that the people who generate the XML use <Vehicle> rather than <vehicle>, but there may be situations where I don't have this freedom, so I'd rather learn a solution than apply a workaround.
Conclusion:
By adding [XmlArrayItem("vehicle", typeof(Vehicle))] to the existing decoration for my List, the XML is now able to de-serialize fully. Phew!
Here's a working pair of classes with appropriate decorations:
(Note: The XmlAnyElement and XmlAnyAttribute are optional. It's a habit I'm in to promote the flexibility of the entity.)
[XmlType("enquiry")]
[XmlRoot("enquiry")]
public class Enquiry
{
private List<Vehicle> vehicles = new List<Vehicle>();
[XmlElement("enquiry_no")]
public int EnquiryNumber { get; set; }
[XmlArray("vehicles")]
[XmlArrayItem("Vehicle", typeof(Vehicle))]
public List<Vehicle> Vehicles
{
get { return this.vehicles; }
set { this.vehicles = value ?? new List<Vehicle>(); }
}
[XmlAnyElement]
public XmlElement[] AnyElements;
[XmlAnyAttribute]
public XmlAttribute[] AnyAttributes;
}
public class Vehicle
{
[XmlElement("vehicle_type")]
public string VehicleType { get; set; }
[XmlElement("vehicle_make")]
public string VehicleMake { get; set; }
[XmlElement("vehicle_model")]
public string VehicleModel { get; set; }
}
I'm trying to deserialize the XML below into class, with the Components deserialized into a List<string>, but can't figure out how to do so. The deserializer is working fine for all the other properties, but not Components. Anyone know how to do this?
<ArsAction>
<CustomerName>Joe Smith</CustomerName>
<LoginID>jdsmith</LoginID>
<TicketGroup>DMS</TicketGroup>
<Software>Visio 2007 Pro</Software>
<Components>
<Component>Component 1</Component>
<Component>Component 2</Component>
</Components>
<Bldg>887</Bldg>
<Room>1320p</Room>
</ArsAction>
Add a property like this to hold the list of Components:
[XmlArray()]
public List<Component> Components { get; set; }
Edit: Sorry I misread that. You want to read it into a collection of strings. I just tried this below and it worked on your sample. The key is just to setup the correct xml serialization attributes.
public class ArsAction
{
[XmlArray]
[XmlArrayItem(ElementName="Component")]
public List<string> Components { get; set; }
}
I have a class that I need to do some custom XML output from, thus I implement the IXmlSerializable interface. However, some of the fields I want to output with the default serialization except I want to change the xml tag names. When I call serializer.Serialize, I get default tag names in the XML. Can I change these somehow?
Here is my code:
public class myClass: IXmlSerializable
{
//Some fields here that I do the custom serializing on
...
// These fields I want the default serialization on except for tag names
public string[] BatchId { get; set; }
...
... ReadXml and GetSchema methods are here ...
public void WriteXml(XmlWriter writer)
{
XmlSerializer serializer = new XmlSerializer(typeof(string[]));
serializer.Serialize(writer, BatchId);
... same for the other fields ...
// This method does my custom xml stuff
writeCustomXml(writer);
}
// My custom xml method is here and works fine
...
}
Here is my Xml output:
<MyClass>
<ArrayOfString>
<string>2643-15-17</string>
<string>2642-15-17</string>
...
</ArrayOfString>
... My custom Xml that is correct ..
</MyClass>
What I want to end up with is:
<MyClass>
<BatchId>
<id>2643-15-17</id>
<id>2642-15-17</id>
...
</BatchId>
... My custom Xml that is correct ..
</MyClass>
In many cases, you can use the XmlSerializer constructor-overload that accepts a XmlAttributeOverrides to specify this extra name information (for example, passing a new XmlRootAttribute) - however, this doesn't work for arrays AFAIK. I expect for the string[] example it would be simpler to just write it manually. In most cases, IXmlSerializable is a lot of extra work - I avoid it as far as possible for reasons like this. Sorry.
You can tag your fields with attributes to control the serialized XML. For example, adding the following attributes:
[XmlArray("BatchId")]
[XmlArrayItem("id")]
public string[] BatchId { get; set; }
will probably get you there.
If anyone is still looking for this you can definitely use the XmlArrayItem however this needs to be a property in a class.
For readability you should use the plural and singular of the same word.
/// <summary>
/// Gets or sets the groups to which the computer is a member.
/// </summary>
[XmlArrayItem("Group")]
public SerializableStringCollection Groups
{
get { return _Groups; }
set { _Groups = value; }
}
private SerializableStringCollection _Groups = new SerializableStringCollection();
<Groups>
<Group>Test</Group>
<Group>Test2</Group>
</Groups>
David