Deserialize XML Response with Incremented Element Names in C# - c#

I am trying to consume a 3rd party API using ASP.NET C# that returns results as seen in the example below and I am getting tripped up on the fact that the child objects have incremented names:
<Results>
<Count>3</Count>
<Result1>
<Id>1</Id>
<Property1>value</Property1>
<Property2>value</Property2>
...
<PropertyN>value</PropertyN>
</Result1>
<Result2>...properties...</Result2>
<Result3>...properties...</Result3>
</Results>
My C# class is as outlined below, and through some research I am assuming that I have to implement IXmlSerializable to handle this somehow:
public class Results : IXmlSerializable
{
[XmlElement("Count")]
public int Count { get; set; }
public List<Result> ResultItems { get; set; }
}
Is this a common pattern for XML and does anyone have any ideas on how to serialize this? I don't work a lot with XML (mostly JSON), so thanks in advance.

Using xml linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication42
{
class Program
{
static void Main(string[] args)
{
string xml =
"<Root>" +
"<Results>" +
"<Count>3</Count>" +
"<Result1>...properties...</Result1>" +
"<Result2>...properties...</Result2>" +
"<Result3>...properties...</Result3>" +
"</Results>" +
"</Root>";
XElement xResults = XElement.Parse(xml);
Results results = xResults.Elements("Results").Select(x => new Results() {
Count = (int)x.Element("Count"),
ResultItems = x.Elements().Where(y => y.Name.LocalName.StartsWith("Result")).Select(y => (string)y).ToList()
}).FirstOrDefault();
}
}
public class Results
{
public int Count { get; set; }
public List<string> ResultItems { get; set; }
}
}

jdweng's answer works, and for those of you who have an option to use that should take it; however, my issue is the data is in a much larger XML response and using the xml serializer instead of XElement works really well with 95% of the fields (the other 5% being the section I specified in the question), so I don't want to jettison that code - instead, I want to implement the IXmlSerializable interface.
After tearing out most of my hair and a little help from this article, I finally figured out how to implement IXmlSerializable for my situation, and I wanted to post the answer for anyone who has a similar issue, as well as community feedback since there didn't seem to be anything on stack overflow about this:
public class Results : IXmlSerializable
{
public int Count { get; set; }
public List<Result> ResultItems { get; set; }
public Results()
{
ResultItems = new List<Result>();
}
public XmlSchema GetSchema()
{
return (null);
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("Results");
if(reader.Name == "Count")
{
Count = reader.ReadElementContentAsInt();
}
for (int i = Count; i > 0; i--)
{
var result = new Result();
reader.ReadStartElement("Result" + i);
result.Property1 = reader.ReadElementContentAsInt();
result.Property2 = reader.ReadElementContentAsString();
...
...
result.PropertyN = reader.ReadElementContentAsString();
ResultItems.Add(result);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
//I don't ever need to write this to XML,
//so I'm not going to implement this
throw new NotImplementedException();
}
}
First, I call reader.ReadToElement() and put the element name in there. When the reader gets passed to the ReadXml method, it is at the beginning of the request result, so you need to get it to the beginning of the object you want to serialize.
Then, I read the first element whose name is Count. I put the value in the Count property and then loop through all of the results. It is important to note that you must read every property of the result object, otherwise you will get exceptions.
Lastly, I read the closing tag and move on with my life. Please let me know what you think about this implementation and if there are any improvements that could be made.
One thing I could not figure out is that I have a results object and the reader has a method that I want to use: reader.ReadElementContentAs(typeof(Result), null) - it seems it would be nicer to do that instead of reading each individual node as I have done in my implementation. Does anyone know how to do that?

Related

xmlserialize argumentexception (ArgumentException: Could not cast or convert from System.String to System.Collections.Generic.List`1[System.String].)

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.

XmlArray with multiple names

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>

What's the easiest way to save an object to a file without serialization attributes?

I have different kinds of objects in c# that I would like to save to a file (XML is preferred) but I can't use serialization since the class are not written by me but are from a DLL.
What is the best solution for this ?
I eventually used JavaScriptSerializer and it does exactly what I was looking for:
List<Person> persons = new List<Person>();
persons.Add(new Person(){Name = "aaa"});
persons.Add(new Person() { Name = "bbb" });
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
var strData = javaScriptSerializer.Serialize(persons);
var persons2 = javaScriptSerializer.Deserialize<List<Person>>(strData);
I've whipped up a quick little extension method that will "serialize" to XML, given a non-serializable object. It's pretty rough and doesn't do a heck of a lot of checking and the XML it generates you can easily tweak to meet your needs:
public static string SerializeObject<T>(this T source, bool serializeNonPublic = false)
{
if (source == null)
{
return null;
}
var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
if (serializeNonPublic)
{
bindingFlags |= BindingFlags.NonPublic;
}
var properties = typeof(T).GetProperties(bindingFlags).Where(property => property.CanRead).ToList();
var sb = new StringBuilder();
using (var writer = XmlWriter.Create(sb))
{
writer.WriteStartElement(typeof(T).Name);
if (properties.Any())
{
foreach (var property in properties)
{
var value = property.GetValue(source, null);
writer.WriteStartElement(property.Name);
writer.WriteAttributeString("Type", property.PropertyType.Name);
writer.WriteAttributeString("Value", value.ToString());
writer.WriteEndElement();
}
}
else if (typeof(T).IsValueType)
{
writer.WriteValue(source.ToString());
}
writer.WriteEndElement();
}
return sb.ToString();
}
I tested it on this class:
private sealed class Test
{
private readonly string name;
private readonly int age;
public Test(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get
{
return this.name;
}
}
public int Age
{
get
{
return this.age;
}
}
}
as well as the number 3 and object. The resulting XML is as such:
<?xml version="1.0" encoding="utf-16"?>
<Test>
<Name Type="String" Value="John Doe" />
<Age Type="Int32" Value="35" />
</Test>
<?xml version="1.0" encoding="utf-16"?>
<Int32>3</Int32>
<?xml version="1.0" encoding="utf-16"?>
<Object />
respectively.
write your own serializable wrappers around the non-serializable classes of the DLL.
EDIT: AutoMapper was suggested in the comments and I hadn't heard of it yet, but now that I have I'd definitely use that instead of writing the wrappers myself. Unless there's some reflection required to capture some of the internal state of the non-serializable object (if possible), I don't know if AutoMapper has anything to offer there or you'd have to see if you could capture that in your wrapper.
I would write a POCO (Plain Old Class Object) class that mimics the object from the DLL returned. Generally if you are using, I believe, .NET 3.5 or higher you have the ability to use LINQ. I favor Linq to put objects into other classes or perform sorting or other operations on them.
Here is a simple example where you would be dummying in your return object for example. Keep in mind in a DLL of course you could have many differing objects and do this multiple times. I also would wrap my methods up in their own class for re usability instead of doing it in the main. But here is a simple proof of concept
using System;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Xml.Linq;
namespace ExampleSerializer
{
class Program
{
// example class to serialize
[Serializable]
public class SQLBit
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlText]
public string data { get; set; }
}
// example class to populate to get test data
public class example
{
public string Name { get; set; }
public string data { get; set; }
}
static void Main(string[] args)
{
string s = "";
// make a generic and put some data in it from the test
var ls = new List<example> { new example { Name = "thing", data = "data" }, new example { Name = "thing2", data = "data2" } };
// make a second generic and put data from the first one in using a lambda
// statement creation method. If your object returned from DLL is a of a
// type that implements IEnumerable it should be able to be used.
var otherlist = ls.Select(n => new SQLBit
{
Name = n.Name,
data = n.data
});
// start a new xml serialization with a type.
XmlSerializer xmler = new XmlSerializer(typeof(List<SQLBit>));
// I use a textwriter to start a new instance of a stream writer
TextWriter twrtr = new StreamWriter(#"C:\Test\Filename.xml");
// Serialize the stream to the location with the list
xmler.Serialize(twrtr, otherlist);
// Close
twrtr.Close();
// TODO: You may want to put this in a try catch wrapper and make up your
// own classes. This is a simple example.
}
}
}
I think the term "without-serialization" in the question header is misleading.
If i understood you correctly you want to serialize objects that have no serilisation-attributes.
There are libraries like sharpserializer and protobuf-net that can do the job for you.

How do I add to an ICollection property when iterating object type?

I have need to add to an ICollection<string> property of a class of which I have an IEnumerable of. Here is a complete program which illustrates the problem:
using System;
using System.Collections.Generic;
using System.Linq;
namespace CollectionAddingTest
{
public class OppDocumentServiceResult
{
public OppDocumentServiceResult()
{
this.Reasons = new List<string>();
}
public Document Document { get; set; }
public bool CanBeCompleted
{
get
{
return !Reasons.Any();
}
}
public ICollection<string> Reasons { get; private set; }
}
public class Document
{
public virtual string Name { get; set; }
}
public class Program
{
private static void Main(string[] args)
{
var docnames = new List<string>(new[] {"test", "test2"});
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
});
foreach (var result in oppDocResult)
{
result.Document.Name = "works?";
result.Reasons.Add("does not stick");
result.Reasons.Add("still does not stick");
}
foreach (var result in oppDocResult)
{
// doesn't write "works?"
Console.WriteLine(result.Document.Name);
foreach (var reason in result.Reasons)
{
// doesn't even get here
Console.WriteLine("\t{0}", reason);
}
}
}
}
}
I would expect that each OppDocumentServiceResult would have its referenced Document.Name
property set to works? and each OppDocumentServiceResult should have two reasons added to it. However, neither is happening.
What is special about the Reasons property that I cannot add things to it?
The problem is that oppDocResult is the result of a LINQ query, using deferred execution.
In other words, every time you iterate over it, the query executes and new OppDocumentServiceResult objects are created. If you put diagnostics in the OppDocumentServiceResult constructor, you'll see that.
So the OppDocumentServiceResult objects you're iterating at the end are different ones to the ones you've added the reasons to.
Now if you add a ToList() call, then that materializes the query into a "plain" collection (a List<OppDocumentServiceResult>). Each time you iterate over that list, it will return references to the same objects - so if you add reasons the first time you iterate over them, then you print out the reasons when you iterate over them again, you'll get the results you're looking for.
See this blog post (among many search results for "LINQ deferred execution") for more details.
The issue is your initial Select you are instantiating new OppDocumentServiceResult objects. Add a ToList and you should be good to go:
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
}).ToList();
As Servy pointed out I should have added a bit more detail to my answer, but thankfully the comment he left on Tallmaris' answer takes care of that. In his answer Jon Skeet further expands on the reason, but what it boils down to "is that oppDocResult is the result of a LINQ query, using deferred execution."
Fixed like this, converting to List instead of keeping the IEnumerable:
var oppDocResult = docnames
.Where(docName => !String.IsNullOrEmpty(docName))
.Select(docName
=> new OppDocumentServiceResult
{
Document = docName
}).ToList();
I can only guess (this is a shot in the dark really!) that the reason behind this is that in an IEnumerable the elements are like "proxies" of the real elements? basically the Enumerable defined by the Linq query is like a "promise" to get all the data, so everytime you iterate you get back the original items? That does not explain why a normal property still sticks...
So, the fix is there but the explanation I am afraid is not... not from me at least :(
ForEach() is defined against only List<T> you will not be able to use it to for an ICollection<T>.
You have to options:
((List<string>) Reasons).ForEach(...)
Or
Reasons.ToList().ForEach(...)
Yet, my preferred approach
I would define this extension which can help automating this for you without wasting resources:
public static class ICollectionExtensions
{
public static void ForEach(this ICollection<T> collection, Action<T> action)
{
var list = collection as List<T>;
if(list==null)
collection.ToList().ForEach(action);
else
list.ForEach(action);
}
}
Now I can use ForEach() against ICollection<T>.
Just change your code inside your class
public List<string> Reasons { get; private set; }

XML deserialization - throwing custom errors

So I have the following method:
private int? myIntField
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public int? IntField{
get {
return this.myIntField;
}
set {
this.myIntField= value;
}
}
Now, I am deserializing xml from a post, if for whatever reason I am getting a string, such as "here is the int field: 55444" instead of 55444, the error I get in response is: Input string was not in a correct format. which isn't very specific, especially considering I will have more than one int field I need to verify.
Originally, I was planning something like this:
private string myIntField
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public int? IntField{
get {
return this.myIntField.CheckValue();
}
set {
this.myIntField= value;
}
}
Where CheckValue performs a try-parse to an Int32, and if it fails it returns a null and adds an error to a list. However, I can't seem to nail this set-up for the generated classes.
Is there I way I can throw a specific error if I am getting strings in place of ints, DateTimes, etc?
It's easy if you have schema(s) for you XML and validate it against schema before deserializing. Suppose you have schema(s) for your XML, you can initialize a XmlSchemaSet, add your schema(s) in it and the:
var document = new XmlDocument();
document.LoadXml(xml); // this a string holding the XML
document.Schemas.XmlResolver = null; //if you don't need to resolve every references
document.Schemas.Add(SchemaSet); // System.Xml.Schema.XmlSchemaSet instance filled with schemas
document.Validate((sender, args) => { ... }); //args are of type ValidationEventArgs and hold problem if there is one...
Personally I think this is a better approach, because you can validate your XML before deserializing and be sure the XML is correct otherwise the deserializer will most probably throw an exception if something is wrong and you will almost never be able to show a meaningful feedback to the user...
P.S. I recommend creating schema(s) describing the XML
The "Input string was not in a correct format" messages comes from a standard System.FormatException raised by a call to int.Parse, added to the automatically generated assembly that does the deserialization. I don't think you can add some custom logic to that.
One solution is to do something like this:
[XmlElement("IntField")]
[Browsable(false)] // not displayed in grids
[EditorBrowsable(EditorBrowsableState.Never)] // not displayed by intellisense
public string IntFieldString
{
get
{
return DoSomeConvert(IntField);
}
set
{
IntField = DoSomeOtherConvert(value);
}
}
[XmlIgnore]
public int? IntField { get; set; }
It's not perfect, because you can still get access to the public IntFieldString, but at least, the "real" IntField property is used only programmatically, but not by the XmlSerializer (XmlIgnore), while the field that's holding the value back & forth is hidden from programmers (EditorBrowsable), grids (Browsable), etc... but not from the XmlSerializer.
I have three approaches for you.
Assuming your data is being entered by a user in a user interface, use input validation to ensure the data is valid. It seems odd to allow random strings to be entered when it should be an integer.
Use exactly the approach you suggest above. Here's an example using LINQ Pad
void Main()
{
using(var stream = new StringReader(
"<Items><Item><IntValue>1</IntValue></Item></Items>"))
{
var serializer = new XmlSerializer(typeof(Container));
var items = (Container)serializer.Deserialize(stream);
items.Dump();
}
}
[XmlRoot("Items")]
public class Container
{
[XmlElement("Item")]
public List<Item> Items { get; set; }
}
public class Item
{
[XmlElement("IntValue")]
public string _IntValue{get;set;}
[XmlIgnore]
public int IntValue
{
get
{
// TODO: check and throw appropriate exception
return Int32.Parse(_IntValue);
}
}
}
Take control of serialization using IXmlSerializable, here's another example
void Main()
{
using(var stream = new StringReader(
"<Items><Item><IntValue>1</IntValue></Item></Items>"))
{
var serializer = new XmlSerializer(typeof(Container));
var items = (Container)serializer.Deserialize(stream);
items.Dump();
}
}
[XmlRoot("Items")]
public class Container
{
[XmlElement("Item")]
public List<Item> Items { get; set; }
}
public class Item : IXmlSerializable
{
public int IntValue{get;set;}
public void WriteXml (XmlWriter writer)
{
writer.WriteElementString("IntValue", IntValue.ToString());
}
public void ReadXml (XmlReader reader)
{
var v = reader.ReadElementString();
// TODO: check and throw appropriate exception
IntValue = int.Parse(v);
}
public XmlSchema GetSchema()
{
return(null);
}
}

Categories