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; }
}
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 am transferring large chunks of data over WCF, and I am trying to optimize the results sent. I have switched from NetDataContactSerializer to DataContractSerializer, but since it is no longer being serialized as XML, I wonder exactly what happens.
As an example, imagine I serialize a collection (100,000 records) of the following to XML:
public class SomeDataObject
{
public string AnExcessivelyLongPropertyNameJustToIllustrateMyPoint { get; set; }
}
It would look something like this:
<a:SomeDataObject>
<b:AnExcessivelyLongPropertyNameJustToIllustrateMyPoint>
ABC
</b:AnExcessivelyLongPropertyNameJustToIllustrateMyPoint>
</a:SomeDataObject>
Now, from the above, it is clear that for hundreds of thousands of records, there is a significant performance gain in naming the property something else, like this:
<a:SomeDataObject>
<b:NormalName>ABC</b:NormalName>
</a:SomeDataObject>
My question is: When using a netTcp binding and the default DataContactSerializer, is it intelligent enough to not actually repeat the names of the properties being serialized?
Or if you don't know the answer to this, is there an easy way to measure this?
For WPF serialization to have smaller XML add short name in Name attribute like:
[DataContract(Name="sdo")]
public class SomeDataObject
{
[DataMember(Name = "axlpnjtimp")]
public string AnExcessivelyLongPropertyNameJustToIllustrateMyPoint { get; set; }
}
For manual XML serialization using XmlSerializer add attribute [XmlType(TypeName = "x")] for class and [XmlElement("y")] for properties.
In your case I would so something like:
[XmlType(TypeName = "sdo")]
public class SomeDataObject
{
[XmlElement("axlpnjtimp")]
public string AnExcessivelyLongPropertyNameJustToIllustrateMyPoint { get; set; }
}
And serialized xml:
<?xml version="1.0" encoding="utf-16"?>
<sdo>
<axlpnjtimp>Property Name</axlpnjtimp>
</sdo>
It will reduce send data size considerably.
In the database, we have an xml field that contains 2 validation schemas; the old one does not have a namespace, the new one does. The reason for this is that we had to version one of the properties. Here is an example of the differences:
Version 1
<PropertyA>
<PropertyA1>false</PropertyA1>
<PropertyA2>3.23</PropertyA2>
</PropertyA>
Version 2
<ts:PropertyA xmlns:ts="http://www.example.com/v2">
<ts:PropertyA1>false</ts:PropertyA2>
<ts:PropertyA2>
<ts:PropertyA2a>
<ts:PropertyA2a1>City 1</ts:PropertyA2a1>
<ts:PropertyA2a2>3.23</ts:PropertyA2a2>
</ts:PropertyA2a>
<ts:PropertyA2b>
<ts:PropertyA2b1>City 2</ts:PropertyA2b1>
<ts:PropertyA2b2>1.21</ts:PropertyA2b2>
</ts:PropertyA2b>
</ts:PropertyA2>
</ts:PropertyA>
Basically, we just create multiple options for PropertyA2...
So now the isue is deserialization. This object needs to be deserialized into the same data object in the app code and the problem is that the element name is the same so the serializer is obviously having trouble figuring out which object to deserialize into since sometimes the database will return Version 1 and sometimes it will return Version 2.
Here is an example of the data class being used for serialization and my current approach that isn't quite working:
[Serializable]
public class MyDataClass
{
// ... other stuff
[XmlElement(Name = "PropertyA", typeof(V1.PropertyA), Namespace = "")]
public V1.PropertyA PropertyAV1 { get ;set; }
[XmlElement(Name = "PropertyA", typeof(V2.PropertyA), Namespace = "http://www.example.com/v2")]
public V2.PropertyA PropertyAV2 { get; set; }
}
[Serializable]
public class V1.PropertyA
{
public bool PropertyA1 { get; set; }
public decimal PropertyA2 { get; set; }
}
[Serializable]
public class V2.PropertyA
{
public bool PropertyA1 { get; set; }
public List<SomeOtherThing> PropertyA2 { get; set; }
}
When I go to deserialize V1, it works fine. When I go to deserialize V2, i get an error Did not expect <ts:PropertyA xmlns:ts="http://www.example.com/v2"> so I'm thinking there's a parameter I'm missing in the deserialize method:
public MyDataClass Deserialize(string xml)
{
var s = new XmlSerializer(typeof (MyDataClass));
MyDataClass info = null;
using (var r = new StringReader(xml))
{
info = (MyDataClass) s.Deserialize(r);
}
return info;
}
I believe you can set the expected namespace in the serializer, but since I don't know what the namespace is going to be until I actually inspect the xml document, I'm not sure how to proceed.
So my question is this: Is what I'm trying to do even possible? Am I on the right track? Is there a better solution that is maybe less contrived? How can I have the serializer deal with the new namespace and deserialize to the correct properties?
You can't.
The problem here is that you have to hardcode MyDataClass according to a single XMLSchema. If the XMLSchema alters, MyDataClass is no longer a valid target for the XMLSerializer's deserialize method, which is why you're getting the 'Did not expect ...' error message. In this case, when reading the V2 xml data stream, the deserialize method tries to fill MyDataClass#PropertyAV1 with the content of <ts:PropertyA2> and there is no way of telling it to instead fill MyDataClass#PropertyAV2. Even if there was a way to achieve this, you'd be stuck with an undefined value for MyDataClass#PropertyAV1 in the object of type MyDataClass.
So there are two solutions to the problem at hand :
a) Stick with XMLSerializer and define class MyDataClass like so
public class MyDataClass
{
// only one Property here, as there's only one root element in the xml
// and this single Property is not bound to a specific XML element
[XmlAnyElement]
public PropertyA PropertyA { get ;set; }
}
You then have to analyze the contents of PropertyA yourself and build some logic around it, see here for more details :
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlanyelementattribute.aspx
b) Dispense the XMLSerializer, read the XML data stream with XMLReader and do the all the parsing of the xml yourself, also add logic to create the according C# objects, depending on the the type of xml you've read.
Obviously, both solutions require more coding on the C# side, but with solution b) you'll have the chance of gaining a performance benefit, as XMLSerializer#deserialize most probably builds a DOM tree to create the C# object from, which the XMLReader doesn't do.
It seems that what I was trying to do was either unachievable or no one with the right level of xml fu saw this thread :(.
So anyway, what I ended up doing was adding an extra column to the database with the version number of the xml contract. Since everything in there was the same, I just called it V1.
I then read that info out into app code and used the version number to drive a factory. Basically, if v1, then deserialize to this, if v2, deserialize to this other thing.
And of course, to support that, I simply created a new data object that had the appropriate structure to support v2. I'm not happy with it, but it works and is flexible enough :/
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 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.