My app serializes objects in streams.
Here is a sample of what I need :
<links>
<link href="/users" rel="users" />
<link href="/features" rel="features" />
</links>
In this case, the object is a collection of 'links' object.
-----------First version
At first I used the DataContractSerializer, however you cannot serialize members as attributes (source)
Here is the object :
[DataContract(Name="link")]
public class LinkV1
{
[DataMember(Name="href")]
public string Url { get; set; }
[DataMember(Name="rel")]
public string Relationship { get; set; }
}
And here is the result :
<ArrayOflink xmlns:i="...." xmlns="...">
<link>
<href>/users</href>
<rel>users</rel>
</link>
<link>
<href>/features</href>
<rel>features</rel>
</link>
</ArrayOflink>
----------- Second version
Ok, not quiet what I want, so I tried the classic XmlSerializer, but... oh nooo, you cannot specify the name of the root element & of the collection's elements if the root element is a collection...
Here is the code :
[XmlRoot("link")]
public class LinkV2
{
[XmlAttribute("href")]
public string Url { get; set; }
[XmlAttribute("rel")]
public string Relationship { get; set; }
}
Here is the result :
<ArrayOfLinkV2>
<LinkV2 href="/users" rel="users" />
<LinkV2 href="/features" rel="features" />
<LinkV2 href="/features/user/{keyUser}" rel="featuresByUser" />
</ArrayOfLinkV2>
----------- Third version
using XmlSerializer + a root element :
[XmlRoot("trick")]
public class TotallyUselessClass
{
[XmlArray("links"), XmlArrayItem("link")]
public List<LinkV2> Links { get; set; }
}
And its result :
<trick>
<links>
<link href="/users" rel="users" />
<link href="/features" rel="features" />
<link href="/features/user/{keyUser}" rel="featuresByUser" />
</links>
</trick>
Nice, but I don't want that root node !!
I want my collection to be the root node.
Here are the contraints :
the serialization code is generic, it works with anything serializable
the inverse operation (deserialization) have to work too
I don't want to regex the result (I serialize directly in an output stream)
What are my solutions now :
Coding my own XmlSerializer
Trick XmlSerializer when it works with a collection (I tried, having it find a XmlRootElement and plurialize it to generate its own XmlRootAttribute, but that causes problem when deserializing + the items name still keeps the class name)
Any idea ?
What really bother me in that issue, is that what I want seems to be really really really simple...
Ok, here is my final solution (hope it helps someone), that can serialize a plain array, List<>, HashSet<>, ...
To achieve this, we'll need to tell the serializer what root node to use, and it's kind of tricky...
1) Use 'XmlType' on the serializable object
[XmlType("link")]
public class LinkFinalVersion
{
[XmlAttribute("href")]
public string Url { get; set; }
[XmlAttribute("rel")]
public string Relationship { get; set; }
}
2) Code a 'smart-root-detector-for-collection' method, that will return a XmlRootAttribute
private XmlRootAttribute XmlRootForCollection(Type type)
{
XmlRootAttribute result = null;
Type typeInner = null;
if(type.IsGenericType)
{
var typeGeneric = type.GetGenericArguments()[0];
var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
if(typeCollection.IsAssignableFrom(type))
typeInner = typeGeneric;
}
else if(typeof (ICollection).IsAssignableFrom(type)
&& type.HasElementType)
{
typeInner = type.GetElementType();
}
// yeepeeh ! if we are working with a collection
if(typeInner != null)
{
var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
if((attributes != null)
&& (attributes.Length > 0))
{
var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
result = new XmlRootAttribute(typeName);
}
}
return result;
}
3) Push that XmlRootAttribute into the serializer
// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);
I told you it was tricky ;)
To improve this, you can :
A) Create a XmlTypeInCollectionAttribute to specify a custom root name (If the basic pluralization does not fit your need)
[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}
B) If possible, cache your XmlSerializer (in a static Dictionary for example).
In my testing, instanciating a XmlSerializer without the XmlRootAttributes takes 3ms.
If you specify an XmlRootAttribute, it takes around 80ms (Just to have a custom root node name !)
XmlSerializer should be able to do what you need, but it is highly dependent on the initial structure and setup. I use it in my own code to generate remarkably similar things.
public class Links<Link> : BaseArrayClass<Link> //use whatever base collection extension you actually need here
{
//...stuff...//
}
public class Link
{
[XmlAttribute("href")]
public string Url { get; set; }
[XmlAttribute("rel")]
public string Relationship { get; set; }
}
now, serializing the Links class should generate exactly what you are looking for.
The problem with XmlSerializer is when you give it generics, it responds with generics. List implemets Array somewhere in there and the serialized result will nearly always be ArrayOf<X>. To get around that you can name the property, or the class root. The closes to what you need is probably the Second Version from your examples. Im assuming you attempted direct serialization of an object List Links. That wouldn't work because you didn't specify the root node. Now, a similar approach can be found here. In this one they specify the XmlRootAttribute when declaring the serializer. yours would look like this:
XmlSerializer xs = new XmlSerializer(typeof(List<Link>), new XmlRootAttribute("Links"));
Here you go...
class Program
{
static void Main(string[] args)
{
Links ls = new Links();
ls.Link.Add(new Link() { Name = "Mike", Url = "www.xml.com" });
ls.Link.Add(new Link() { Name = "Jim", Url = "www.xml.com" });
ls.Link.Add(new Link() { Name = "Peter", Url = "www.xml.com" });
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Links));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, ls);
string serializedXML = stringWriter.ToString();
Console.WriteLine(serializedXML);
Console.ReadLine();
}
}
[XmlRoot("Links")]
public class Links
{
public Links()
{
Link = new List<Link>();
}
[XmlElement]
public List<Link> Link { get; set; }
}
[XmlType("Link")]
public class Link
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlAttribute("Href")]
public string Url { get; set; }
}
Related
Below piece of code is failing and throwing ArgumentException
static void Main(string[] args)
{
string xml = "<root><SourcePatient><Communication>HP:6055550120</Communication></SourcePatient></root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var serializedString = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None,true);
var deserialise = serializedString.ToObject<SampleModel>();
}
Models are,
public class SampleModel
{
public SourcePatientModel SourcePatient { get; set; }
}
public class SourcePatientModel
{
public List<string> Communication { get; set; }
}
How to deserialize this? Sometimes Communication node from xml string will have multiple entries
Your current xml is only a single entry
<Communication>HP:6055550120</Communication>
Change your xml input
<Communication><Entry>HP:6055550120</Entry></Communication>
So later when you get multiple entries, they can be processed
<Communication><Entry>HP:6055550120</Entry><Entry>HP:xxxxxxxxx</Entry></Communication>
Your class needs tweaked a bit
if a string [] is acceptable
[XmlArray(ElementName = "Communication")]
[XmlArrayItem(ElementName = "Entry")]
public string[] comm_list // Whatever name you want here
{
get; set;
}
// if you want a list here
// also if your going to do this, realize it creates a new list every time you use it, not the best. (bad practice)
List<string> Communication
{
get => new List<string>(comm_list );
}
otherwise it gets a little complicated
[XmlRoot(ElementName="Communication")]
public class Communication // element name by def
{
[XmlElement(ElementName="Entry")]
public List<string> entry { get; set; }
}
Another possibility, not sure how your multiple entries come in.
If multiple entries look like the following
<Communication>HP:6055550120, HP:7055550120</Communication>
Then you cant have a direct list,
public class SourcePatientModel
{
public string Communication { get; set; }
// which again this creates a list everytime, its better to change your xml to match a tag name for each entry
[XmlIgnore]
public List<string> CommunicationValues { get => Communication.Split(',').ToList();
}
Also this is just typed up code, there may be some typos or compile errors
First I parsed xml to the valid model, then you can convert to json if needed
using (TextReader sr = new StringReader(xml))
{
XmlSerializer serializer = new XmlSerializer(typeof(SampleModel));
var schema = (SampleModel)serializer.Deserialize(sr);
}
[Serializable, XmlRoot("root")]
public class SampleModel
{
public SourcePatientModel SourcePatient { get; set; }
}
public class SourcePatientModel
{
[XmlElement("Communication")]
public List<string> Communication { get; set; }
}
I modified your classes to include some XML serialization attributes. (More on how to figure those out later - the short version is that you don't have to figure it out.)
[XmlRoot(ElementName = "SourcePatient")]
public class SourcePatientModel
{
[XmlElement(ElementName = "Communication")]
public List<string> Communication { get; set; }
}
[XmlRoot(ElementName = "root")]
public class SampleModel
{
[XmlElement(ElementName = "SourcePatient")]
public SourcePatientModel SourcePatientModel { get; set; }
}
...and this code deserializes it:
var serializer = new XmlSerializer(typeof(SampleModel));
using var stringReader = new StringReader(xmlString);
SampleModel deserialized = (SampleModel) serializer.Deserialize(stringReader);
Here's a unit test to make sure a test XML string is deserialized with a list of strings as expected. A unit test is a little easier to run and repeat than a console app. Using them makes writing code a lot easier.
[Test]
public void DeserializationTest()
{
string xmlString = #"
<root>
<SourcePatient>
<Communication>A</Communication>
<Communication>B</Communication>
</SourcePatient>
</root>";
var serializer = new XmlSerializer(typeof(SampleModel));
using var stringReader = new StringReader(xmlString);
SampleModel deserialized = (SampleModel) serializer.Deserialize(stringReader);
Assert.AreEqual(2, deserialized.SourcePatientModel.Communication.Count);
}
Here's a key takeaway: You don't need to memorize XML serialization attributes. I don't bother because I find them confusing. Instead, google "XML to Csharp" and you'll find sites like this one. I pasted your XML into that site and let it generate the classes for me. (Then I renamed them so that they matched your question.)
But be sure that you include enough sample data in your XML so that it can generate the classes for you. I made sure there were two Communication elements so it would create a List<string> in the generated class.
Sites like that might not work for extremely complicated XML, but they work for most scenarios, and it's much easier than figuring out how to write the classes ourselves.
Some (external) genius decided to provide us with XML:
<message_X>
<header>
<foo>Foo</foo>
<bar>Bar</bar>
</header>
<body>
<blah>
<yadda1 />
<yadda2 />
<yadda3 />
<contentX>
<!-- message_X specific content -->
</contentX>
</blah>
</body>
</message_X>
However, there are also other messages (say, message_Y and message_Z). These all have the exact same basic structure besides what is in the content node and, the reason for this question, the differing root-nodes:
<message_Y>
<header>
<foo>Foo</foo>
<bar>Bar</bar>
</header>
<body>
<blah>
<yadda1 />
<yadda2 />
<yadda3 />
<contentY>
<!-- message_X specific content -->
</contentY>
</blah>
</body>
</message_Y>
Why the root node isn't just named <message>, as I would've done it baffles me. Who comes up with this?
I have created an abstract class Message accordingly:
public abstract class Message {
public Header Header { get; set; }
public Body Body { get; set; }
}
public class Header {
public string Foo { get; set; }
public string Bar { get; set; }
}
// Etc...
I was hoping I could then do this:
[XmlInclude(typeof(XMessage))]
[XmlInclude(typeof(YMessage))]
public abstract class Message {
// ...
}
[XmlRoot("message_X")]
public class XMessage : Message {
// ...
}
[XmlRoot("message_Y")]
public class YMessage : Message {
// ...
}
But that doesn't work: InvalidOperationException: <message_X xmlns=''> was not expected.. To deserialize I use:
var ser = new XmlSerializer(typeof(Message));
using (var sr = new StringReader(xmlString))
return (Message)ser.Deserialize(sr);
I have no control over the XML and I'm not looking forward to implement this message again and again for each X, Y and Z.
I'll sort the Content part out by probably making Message into Message<T> and inheriting with specifying T etc. but that'll be of later concern.
I have also tried specifying Message as Type and XMessage and YMessage as ExtraTypes of the XmlSerializer Constructor but that didn't help either. I have also tried to go the DataContractSerializer route with DataContract, KnownType etc. annotations but this didn't work either.
I would appreciate tips / pointers on how to solve this in a clean fashion.
With #steve16351's** ideas I wrote the following deserializer (I'm not interested in serializing, only deserializing):
public class XmlDeserializer<T>
where T : class
{
// "Globally" caches T => Dictionary<xmlelementnames, type>
private static readonly ConcurrentDictionary<Type, IDictionary<string, Type>> _typecache = new ConcurrentDictionary<Type, IDictionary<string, Type>>();
// We store instances of serializers per type T in this pool so we need not create a new one each time
private static readonly ConcurrentDictionary<Type, XmlSerializer> _serializers = new ConcurrentDictionary<Type, XmlSerializer>();
// And all serializers get the same instance of XmlReaderSettings which, again saves creating objects / garbage collecting.
private static readonly XmlReaderSettings _readersettings = new XmlReaderSettings() { IgnoreWhitespace = true };
// Lookup for current T, with this we keep a reference for the current T in the global cache so we need one less dictionary lookup
private readonly IDictionary<string, Type> _thistypedict;
public XmlDeserializer()
{
// Enumerate T's XmlInclude attributes
var includes = ((IEnumerable<XmlIncludeAttribute>)typeof(T).GetCustomAttributes(typeof(XmlIncludeAttribute), true));
// Get all the mappings
var mappings = includes.Select(a => new
{
a.Type,
((XmlRootAttribute)a.Type.GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault())?.ElementName
}).Where(m => !string.IsNullOrEmpty(m.ElementName));
// Store all mappings in our current instance and at the same time store the mappings for T in our "global cache"
_thistypedict = _typecache.GetOrAdd(typeof(T), mappings.ToDictionary(v => v.ElementName, v => v.Type));
}
public T Deserialize(string input)
{
// Read our input
using (var stringReader = new StringReader(input))
using (var xmlReader = XmlReader.Create(stringReader, _readersettings))
{
xmlReader.MoveToContent();
// Make sure we know how to deserialize this element
if (!_thistypedict.TryGetValue(xmlReader.Name, out var type))
throw new InvalidOperationException($"Unable to deserialize type '{xmlReader.Name}'");
// Grab serializer from pool or create one if required
var serializer = _serializers.GetOrAdd(type, (t) => new XmlSerializer(t, new XmlRootAttribute(xmlReader.Name)));
// Finally, now deserialize...
return (T)serializer.Deserialize(xmlReader);
}
}
}
This deserializer uses the XmlInclude attributes to figure out which classes are to be mapped with what element-name using the XmlRoot attributes. Usage couldn't be simpler:
var ser = new XmlDeserializer<Message>();
ser.Deserialize("<message_X>...");
It does some internal 'caching' and 'pooling' of objects to keep it memory/GC-friendly and quite performant. So this solves my root-node-different-name-for-each-type problem. Now I need to figure out how I'm going to handle the different content-nodes...
** Who has since deleted his answer for some unknown reason...
The second part of the question, the 'generic content' was easily solved:
public class Blah {
public Yadda1 Yadda1 { get; set; }
public Yadda2 Yadda2 { get; set; }
public Yadda3 Yadda3 { get; set; }
[XmlElement("contentX", typeof(ContentX))]
[XmlElement("contentY", typeof(ContentY))]
[XmlChoiceIdentifier(nameof(PayloadType))]
public Payload Payload { get; set; }
public PayloadType PayloadType { get; set; }
}
public abstract class Payload { ... }
public class ContentX : Payload { ... }
public class ContentY : Payload { ... }
[XmlType]
public enum PayloadType
{
[XmlEnum("contentX")]
ContentX,
[XmlEnum("contentY")]
ContentY,
}
This only leaves me wondering if I could apply the same idea on the root node...
I've searched for hours and found similar results but not for this specific scenario. Consider the following XML file.
<root>
<largeImages>
<largeImage>
<url>./imageLarge.jpg</url>
<height>480</height>
<width>640</width>
</largeImage>
</largeImages>
<smallImages>
<smallImage>
<url>./imageSmall.jpg</url>
<height>240</height>
<width>320</width>
</smallImage>
</smallImages>
</root>
What I'm trying to do is deserialize this into a single array of images instead of 2 arrays.
public class root {
[XmlArray("largeImages")]
[XmlArrayItem("largeImage")]
public image[] largeImages { get; set; }
[XmlArray("smallImages")]
[XmlArrayItem("smallImage")]
public image[] smallImages { get; set; }
}
This class gets me 2 arrays. root.largeImages and root.smallImages. Since my application doesn't care about large or small images I'd like to deserialize into the single array root.images. I've tried variations of XmlArray, XmlArrayItem, XmlElement and even XmlChoiceIdentifier without any success. I'm thinking something along the lines of the following which won't compile because apparently the XmlArrayAttribute can only be used once per property.
[XmlArray("largeImages")]
[XmlArray("smallImages")]
[XmlArrayItem("largeImage")]
[XmlArrayItem("smallImage")]
public image[] images { get; set; }
Obviously I could merge the 2 arrays in code after the XML is deserialized but it seems like this should be a simple thing to do.
XPATH is probably your answer assuming you don't really care about having it mapped to a class. XPath wildcards on node name gives an example of how you'd select multiple items - http://csharp.net-tutorials.com/xml/using-xpath-with-the-xmldocument-class/ gives an example of how it's used in C#.
Another way muight be using XSLT: Using the code: How to apply an XSLT Stylesheet in C# and XSLT like Combining elements from 2 lists in XSLT should get you what you want.
Personally I'd go with whatever makes life easiest since it doesn't look like you really care in this example what the intermediate data structure is.
Given that the answer from How to define multiple names for XmlElement field? works primarily for elements not arrays, the easiest solution would seem to be to introduce a surrogate property for one of the image elements, say <smallImages>:
public class root
{
[XmlArray("largeImages")]
[XmlArrayItem("largeImage")]
public List<image> Images { get; set; } = new List<image>();
[XmlArray("smallImages")]
[XmlArrayItem("smallImage")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public List<image> SmallImagesSurrogate { get { return Images; } }
public bool ShouldSerializeSmallImagesSurrogate() { return false; }
}
Notes:
When deserializing to a pre-allocated List<T>, XmlSerializer will simply append deserialized elements to the existing list. That allows elements from both <largeImages> and <smallImages> to be concatenated into the same collection without data loss.
The surrogate property must be public but can be made "less visible" by setting attributes such as [Browsable(false)].
XmlSerializer can successfully deserialize get-only collection properties as long as the collection is pre-allocated. I took advantage of this to avoid adding a setter for the surrogate.
In case you need to re-serialize your root, the method ShouldSerializeSmallImagesSurrogate() will prevent the images array from being double-serialized. (For an explanation of why, see ShouldSerialize*() vs *Specified Conditional Serialization Pattern.) Instead all the images will serialize under <largeImages>. This method does not affect deserialization.
Sample working .Net fiddle.
Simple!!! I do it all the time. The trick is with root that you need to use Elements() and then use FirstOrDefault() to get only one.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Root root = doc.Elements("root").Select(x => new Root() {
Images = x.Descendants("largeImage").Select(z => new Image() {
url = (string)z.Element("url"),
height = (int)z.Element("height"),
width = (int)z.Element("width")
}).ToList()
}).FirstOrDefault();
root.Images.AddRange(doc.Descendants("smallImage").Select(z => new Image() {
url = (string)z.Element("url"),
height = (int)z.Element("height"),
width = (int)z.Element("width")
}).ToList());
}
}
public class Root
{
public List<Image> Images { get; set; }
}
public class Image
{
public string url { get; set; }
public int height { get; set; }
public int width { get; set; }
}
}
I had a challenge with model serialization, and thanks to MSDN I found the answer. Here is my solution:
public class Document
{
[XmlElement(ElementName = "seller_id")]
public string SellerId { get; set; }
[XmlArray(ElementName = "order_details")]
[XmlArrayItem(Type = typeof(SgtinCode), ElementName = "sgtin")]
[XmlArrayItem(Type = typeof(SsccCode), ElementName = "sscc")]
public Code[] Codes { get; set; }
}
public abstract class Code
{
[XmlText]
public string Value { get; set; }
}
public class SgtinCode : Code
{ }
public class SsccCode : Code
{ }
Model setup:
var document = new Document
{
SellerId = Guid.NewGuid().ToString(),
Codes = new Code[]
{
new SsccCode { Value = "111700126101510000000000011" },
new SsccCode { Value ="111700126101510000000000012" },
new SsccCode { Value ="111700126101510000000000013" },
new SgtinCode { Value = "abc" }
}
}
And XML output:
<?xml version="1.0" encoding="utf-8"?>
<documents>
<foreign_shipment>
<seller_id>fb2d35e7-c5d1-43ad-a272-89f897f41058</seller_id>
<order_details>
<sscc>111700126101510000000000011</sscc>
<sscc>111700126101510000000000012</sscc>
<sscc>111700126101510000000000013</sscc>
<sgtin>abc</sgtin>
</order_details>
</foreign_shipment>
</documents>
So this has had me stumped for hours... I have an xml structure that looks like this:
<custom>
<priceLower>999999</priceLower>
<priceUpper>1000001</priceUpper>
<investment>true</investment>
<offtheplan>false</offtheplan>
<office>
<name>Melbourne Office</name>
<officeName>Head Office</officeName>
... more elements removed
</office>
</custom>
In my application I have a Custom class, which deserializes from the above xml fine, defined as follows:
[Serializable]
public class Custom : BaseEntity, IDataModel
{
[XmlElement("investment")]
public string Investment { get; set; }
[XmlElement("offtheplan")]
public string Offtheplan { get; set; }
[XmlElement("priceLower")]
public Decimal? PriceLower { get; set; }
[XmlElement("priceUpper")]
public Decimal? PriceUpper { get; set; }
[XmlElement("office")]
public Office Office { get; set; }
And my Office Object defined as follows:
[Serializable]
public class Office : BaseEntity, IDataModel
{
// temporary for debugging purposes:
private string _officeName;
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("officeName")]
public string OfficeName { get; set; }
[XmlElement("addressL1")]
public string Address1 { get; set; }
... more fields removed }
The Deserialize code (called by a helper class, and receives a Property Object containing A Custom object, which contains an Office object) looks like this:
XmlSerializer s = null;
XmlAttributeOverrides attrOverrides = null;
/// if it's a Residential type, do it this way
if (typeof(T) == typeof(Residential))
{
attrOverrides = new XmlAttributeOverrides();
var attrs = new XmlAttributes();
var attr = new XmlElementAttribute();
attr.ElementName = "office";
attr.Type = typeof(Office);
attrs.XmlElements.Add(attr);
attrOverrides.Add(typeof(Office), "office", attrs);
s = new XmlSerializer(typeof(T), attrOverrides);
}
s = attrOverrides == null
? new XmlSerializer(typeof(T))
: new XmlSerializer(typeof(T), attrOverrides);
var obj = s.Deserialize(stream);
return (T)obj;
SO... The Custom object deserializes perfectly.. no issues there. But the office one doesn't - all it's properties always come through as null.
Is there a way to specify exactly which element in the xml tree contains the data for the Office object? i've tried moving the Office Object to sit at the same level as Custom (on the Property object) which makes more sense really, but that didn't work either - i moved it to be under Custom to match the xml structure as I can't change that, and i couldn't find a way to specify where it should get it's data from.
Another bit of weirdness I've experienced here... I have added a Serializer function, which basically creates a new XML file from the deserialized objects. I can debug all the way through to where the Serialize function is called - and if I peek inside the object getting serialized before it happens, i can see that the Office object contains only null values. But the serializer actually serializes data into my new XML file.
Here is where it gets even weirder. If i peek into the object before Serialize() is called, then it will always serialize an empty element. BUT, if i don't peek inside that object before the serialization happens, it'll serialize data into there. I have verified this a bunch of times - without fail that is the behaviour. Has anybody ever seen anything like this? Is the framework playing tricks on me, and if so whY???
UPDATE:
Just to clarify, my XML looks something like this (I only showed an exert above):
<propertyList>
<residential>
<custom>
<property1>
<office>
<officeName>
Office Name Here
</officeName>
</office>
</custom>
</residential>
</propertyList>
So there's a few levels of nesting there, which i possibly the issue, though I'm thinking it's more of a VS issue.
The deserializer is working on the full XML, and deserialising down to a class structure like this:
Residential : Property : BasePropertyType
Contains Custom Object
Contains Office Object
Both Custom and Office objects are instantiated on the Residential object. I have tried putting the Office object on the Custom object instead (to match the xml structure) but that made no difference. Custom serializes correctly, Office does not.
Is it at all possible that the Visual Studio debugger is the red herring here. As i mentioned before, if i debug and take a look at the deserialized object, it shows it as empty, and when i then serialize it back to XML it comes through as empty. But if i don't debug and just step over without taking a look at the object, all the properties serialize correctly to the XML. It's making it kind of difficult to move on and do the rest of the data processing i need to when i can't debug what's going on there. Has anybody seen behaviour in VS like this before??
As to your first question, XmlSerializer can handle simple hierarchies so in your example XmlAttributeOverrides are not necessary:
[Serializable]
[XmlRoot("custom")]
public class Custom
{
[XmlElement("investment")]
public string Investment { get; set; }
[XmlElement("offtheplan")]
public string Offtheplan { get; set; }
[XmlElement("priceLower")]
public Decimal? PriceLower { get; set; }
[XmlElement("priceUpper")]
public Decimal? PriceUpper { get; set; }
[XmlElement("office")]
public Office Office { get; set; }
}
[Serializable]
public class Office
{
// temporary for debugging purposes:
private string _officeName;
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("officeName")]
public string OfficeName { get; set; }
[XmlElement("addressL1")]
public string Address1 { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml = #"<custom>
<priceLower>999999</priceLower>
<priceUpper>1000001</priceUpper>
<investment>true</investment>
<offtheplan>false</offtheplan>
<office>
<name>Melbourne Office</name>
<officeName>Head Office</officeName>
</office>
</custom>";
XmlSerializer s = new XmlSerializer(typeof(Custom));
// Works fine without this
//XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
//var attrs = new XmlAttributes();
//var attr = new XmlElementAttribute();
//attr.ElementName = "office";
//attr.Type = typeof(Office);
//attrs.XmlElements.Add(attr);
//attrOverrides.Add(typeof(Office), "office", attrs);
//s = new XmlSerializer(typeof(Custom), attrOverrides);
using (StringReader reader = new StringReader(xml))
{
Custom c = (Custom)s.Deserialize(reader);
}
}
}
Ahahahaha... I'm such a goof! Had a workmate run through this with me, and we found something really silly that i'd done, namely:
public string ToString()
{
Name = null;
OfficeName = null;
Address1 = null;
Address2 = null;
City = null;
State = null;
Postcode = null;
Phone = null;
Banner = null;
Logo = null;
StringBuilder sb = new StringBuilder();
sb.Append(String.Format("Name:{0} / OfficeName: {1} / Address1: {2} / Address2: {3} / City: {4} / State: {5} / Postcode: {6} / Phone: {7} / Banner: {8} / Logo: {9}",
Name, OfficeName, Address1, Address2, City, State, Postcode, Phone, Banner, Logo));
return sb.ToString();
}
So every time i took a look at the object in the debugger, it was calling my ToString() override, which was then nuking all the values.
Don't i feel sheepish. LOL
I have 2 classes:
public class LocalizationEntry
{
public List<TranslationPair> Translations
{
get;
set;
}
}
public class TranslationPair
{
public string Language
{
get;
set;
}
public string Translation
{
get;
set;
}
}
Is it possible to get such a XML using standard serializer?
<LocalizationEntry>
<Translations>
<EN>apple<EN>
<PL>jabłko<PL>
<DE>apfel<DE>
</Translations>
</LocalizationEntry>
I was thinking about something like XmlArrayItem(ElementName=this.Language) attribute but of course this construction is illegal apart from that attribute value cannot be determined in runtime.
Thanks for any help. Surely I can use other structure, but I'm curious is it possible.
To customise the element names is a pain; I would forget about XmlSerializer here, as you'd have to use IXmlSerializable which is a royal pain. I'd use XElement instead:
var source = new LocalizationEntry {
Translations = new List<TranslationPair> {
new TranslationPair {Language="EN", Translation="apple"},
new TranslationPair {Language="PL", Translation="jabłko"},
new TranslationPair {Language="DE", Translation="apfel"},
}
};
var el = new XElement("LocalizationEntry",
new XElement("Translations",
from pair in source.Translations
select new XElement(pair.Language, pair.Translation)));
var xml = el.ToString();
enter link description hereUse the XMLSerializer Object in the .NET Framework
Remember to put the Serializable Attribute on the Class Name
click this Link, i cant work out how to Add the Name of the URL sorry