DataContractSerializer Case Sensitivity - c#

I need to deserialize a raw xml to a particular object. However I am having issues when it comes to boolean and enum types, as case sensitivity is intact.
public MyObjectTypeDeserializeMethod(string rawXML)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(MyObjectType));
MyObjectType tempMyObject = null;
try
{
// Use Memory Stream
using (MemoryStream memoryStream = new MemoryStream())
{
// Use Stream Writer
using (StreamWriter streamWriter = new StreamWriter(memoryStream))
{
// Write and Flush
streamWriter.Write(rawXML);
streamWriter.Flush();
// Read
memoryStream.Position = 0;
tempMyObject = (MyObjectType)serializer.ReadObject(memoryStream);
}
}
}
catch (Exception e)
{
throw e;
}
return tempMyObject;
}
public class MyObjectType
{
public bool boolValue {get; set;}
public MyEnumType enumValue {get; set;}
}
If the raw XML contains
<boolValue>true</boolValue>
it works fine. However it throws an exception whenever the value is different to the the previous, such as
<boolValue>True</boolValue>
How can this issue be solved in order to allow case insensitive boolean and enum values to be passed from the raw XML?

The xml specification defines xml to be case sensitive, and defines booleans to be the (note case) literals true and false. DataContractSerializer is doing it right. If the value is True, then it isn't an xml boolean, and should be treated as a string.

There are many way to resolve this. An simple way is this approach:
public class MyObjectType
{
[XmlIgnore] public bool BoolValue; // this is not mapping directly from the xml
[XmlElement("boolValue")]
public string BoolInternalValue // this is mapping directly from the xml and assign the value to the BoolValue property
{
get { return BoolValue.ToString(); }
set
{
bool.TryParse(value, out BoolValue);
}
}
...
and I use XmlSerializer for Deserialize the xml:
public static T Deserialize<T>(string xmlContent)
{
T result;
var xmlSerializer = new XmlSerializer(typeof(T));
using (TextReader textReader = new StringReader(xmlContent))
{
result = ((T)xmlSerializer.Deserialize(textReader));
}
return result;
}

Related

Parse exponential Value in Xml document

I have an issue when deserializing an object (which I cannot modify).
I receive an exponential value for certain xml element and for represent them in my class I used decimal value expect that when I deserialize the xml document it fails.
<fwdRate>-9.72316862724032E-05</fwdRate>
Is there any solution to represent this attribute other than create 2 attributes in my class to represent it (one string and the other a decimal value)?
Can I create a custom deserializtion class for decimal value?
private void ParseXML(string value)
{
XmlSerializer serializer = new XmlSerializer(typeof(SwapDataSynapseResult));
using (TextReader reader = new StringReader(value))
{
_result = serializer.Deserialize(reader) as SwapDataSynapseResult;
}
}
As Demand
using System;
using System.IO;
using System.Xml.Serialization;
[XmlRoot(ElementName = "result")]
public class Result
{
[XmlElement(ElementName = "fwdRate")]
public decimal FwdRate { get; set; }
}
public class Program
{
public static void Main()
{
string val = "<result><fwdRate>-9.72316862724032E-05</fwdRate></result>";
Result response = ParseXML(val);
}
static Result ParseXML(string value)
{
XmlSerializer serializer = new XmlSerializer(typeof(Result));
using (TextReader reader = new StringReader(value))
{
return serializer.Deserialize(reader) as Result;
}
}
}
In XML, decimal values are not allowed to use scientific (exponential) notation (See this link at the 'Restrictions' paragraph).
Either:
the value is indeed a floating point one: Put a float/double instead of a decimal in the code.
the XML is corrupted.
In the same way, in C#, by default, Decimal.Parse doesn't accept exponential representation.
You can override this behavior by implementing a new struct that wrap a decimal and implement IXmlSerializable and allow exponential representation when de-serialized:
public struct XmlDecimal : IXmlSerializable
{
public decimal Value { get; private set; }
public XmlDecimal(decimal value) => Value = value;
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
var s = reader.ReadElementContentAsString();
Value = decimal.TryParse(s, NumberStyles.Number | NumberStyles.AllowExponent,
NumberFormatInfo.InvariantInfo, out var value)
? value
: 0; // If parse fail the resulting value is 0. Maybe we can throw an exception here.
}
public void WriteXml(XmlWriter writer) => writer.WriteValue(Value);
public static implicit operator decimal(XmlDecimal v) => v.Value;
public override string ToString() => Value.ToString();
}
The flaw is that you have to use this struct instead of a decimal everywhere in your model.
And sadly you can't make this struct read-only, has explained here.
The proper way is to control how your properties are deserialized by implementing the IXmlSerializable interface:
IXmlSerializable
IXmlSerializable code project example
In the ReadXml method you should convert your number
var yourStringNumber = ...
this.fwdRate = Decimal.Parse(yourStringNumber, System.Globalization.NumberStyles.Float);
But this method will require you to parse of the whole xml manually that is a bit overhead sometimes.
A simple solution (that smells but might be useful) is just to add additional fwdRateDecimal field to your class and fulfill the value after the serialization.
private void ParseXML(string value)
{
XmlSerializer serializer = new XmlSerializer(typeof(SwapDataSynapseResult));
using (TextReader reader = new StringReader(value))
{
_result = serializer.Deserialize(reader) as SwapDataSynapseResult;
_result.fwdRateDecimal = Decimal.Parse(_result.fwdRate, System.Globalization.NumberStyles.Float)
}
}
Also conversion can be implemented in a type directly:
[XmlRoot(ElementName = "result")]
public class Result
{
[XmlElement(ElementName = "fwdRate")]
public string FwdRateStr { get; set; }
private string lastParsedValue = null;
private decimal fwdRate = 0;
[XmlIgnore]
public decimal FwdRate
{
get
{
if(FwdRateStr != lastParsedValue)
{
lastParsedValue = FwdRateStr
fwdRate = Decimal.Parse(FwdRateStr ,System.Globalization.NumberStyles.Float)
}
return fwdRate
}
}
This is the solution:
using System;
using System.IO;
using System.Xml.Serialization;
[XmlRoot(ElementName = "result")]
public class Result
{
[XmlElement(ElementName = "fwdRate")]
public double FwdRate { get; set; }
}
public class Program
{
public static void Main()
{
string val = "<result><fwdRate>-9.72316862724032E-05</fwdRate></result>";
Result response = ParseXML(val);
Console.WriteLine(response.FwdRate);
}
static Result ParseXML(string value)
{
XmlSerializer serializer = new XmlSerializer(typeof(Result));
using (TextReader reader = new StringReader(value))
{
return serializer.Deserialize(reader) as Result;
}
}
}
You can't modify the incoming xml but you can modify how you read its' data.
Read that number as a double instead of decimal and you will have the right decimal precision.
Output:
-9,72316862724032E-05

Separate object fields into two files by serializing DataContract

I an using DataContractSerializer and i want to separate data of same object into multiple files.
[DataContract]
public class TestObj
{
[DataMember]
protected double field1 = 0.0;
[DataMember]
protected double field2 = 0.0;
}
Specifically I want to save field1 in one XML file and field2 in a different XML file. Is there any way to do that using data contract serialization?
This is how I am serializing currently:
DataContractSerializer serializaer = new DataContractSerializer(GetType(), null,
0x7FFFFFFF /*maxItemsInObjectGraph*/,
false /*ignoreExtensionDataObject*/,
true /*preserveObjectReferences : this is most important to me */,
null /*dataContractSurrogate*/);
var fs = File.Open(fName, FileMode.Create);
serializaer.WriteObject(fs, this);
fs.Dispose();
return true;
I can suggest using Custom Xml Writer paired with serializer.
public class CustomWriter : XmlTextWriter
{
public CustomWriter(TextWriter writer) : base(writer) { }
public CustomWriter(Stream stream, Encoding encoding) : base(stream, encoding) { }
public CustomWriter(string filename, Encoding encoding) : base(filename, encoding) { }
public List<string> FieldList { get; set; }
private string _localName;
public override void WriteStartElement(string prefix, string localName, string ns)
{
if (!FieldList.Contains(localName))
base.WriteStartElement(prefix, localName, ns);
else
_localName = localName;
}
public override void WriteString(string text)
{
if (!FieldList.Contains(_localName))
base.WriteString(text);
}
public override void WriteEndElement()
{
if (!FieldList.Contains(_localName))
base.WriteEndElement();
else
_localName = null;
}
}
Use it as follows:
var data = new TestObj();
var serializer = new DataContractSerializer(
typeof(TestObj), null, 0x7FFFFFFF, false, true, null);
using (var writer = new CustomWriter(Console.Out)) // Specify filename or stream instead
{
writer.Formatting = Formatting.Indented;
writer.FieldList = new List<string> { "field1", "field3" }; // Specify fields to ignore
serializer.WriteObject(writer, data);
}
Just specify a list of fields that should be ignored in the FieldList property.
Of course, with this way, the DataContractSerializer will intermediate create xml with all the elements contained in the class. And only after that our custom writer will filter them. But it will happen on the fly, very quickly and effectively.
Once you serialise the whole object you can then split it using Linq to XML.
Oh, I even found an example how to do it at How to Split an XML file into multiple XML Files.

Deserializing xml string to an object and I get empty list of missing list element. Why does it not return null?

public class Warning
{
public Warning()
{
}
public Warning(String name, List<String> conflictList)
{
Name = name;
ConflictList = conflictList;
}
public string Name
{
get;
set;
}
public List<String> ConflictList
{
get;
set;
}
public static Warning ReadXML(string xml)
{
Warning warning = null;
try
{
XmlSerializer serializer = new XmlSerializer(typeof(Warning));
using (StringReader sr = new StringReader(xml))
{
XmlTextReader xtr = new XmlTextReader(sr);
warning = (Warning)serializer.Deserialize(xtr);
xtr.Close();
sr.Close();
}
}
catch
{
}
return warning;
}
public void Save(string filename)
{
XmlSerializer serializer = new XmlSerializer(typeof(Warning));
using (StreamWriter writer = new StreamWriter(filename))
{
serializer.Serialize(writer, this);
writer.Close();
}
}
}
If I deserializing the following xml string and I get non-null values back for both Name and ConflictList. This is fine and what I expect.
Warning w1 = Warning.ReadXML(
"<Warning><Name>test warning</Name><ConflictList><string>file1.txt</string></ConflictList></Warning>");
w1.Name returns "test warning" and w1.ConflictList returns a list containing the text "file1.txt"
However, if I deserialize the following xml string, w2.Name returns null, which i understand, but ConflictList is actually a list with a Count of 0. I expected it to be null as well. How come?
Warning w2 = Warning.ReadXML("<Warning></Warning>");
I am sure in some cases w1.ConflictList can return null if the element is not present in the xml string, but when does this happen?
I too would have expected it to leave it alone if it isn't in the xml, but I too can reproduce what you are saying, both "as is", and with [XmlElement(...)] on the member. I guess internally it is newing up the list "in case".
I also tried the alternative syntax just using a get and a lazy initializer, i.e.
// note: doesn't work; see answer text
private List<string> conflictList;
public List<String> ConflictList
{
get { return conflictList ?? (conflictList = new List<string>()); }
}
but it still invokes this getter, even when no conflict data is included. And annoyingly the *Specified pattern only ever gets called for data that is specified - it doesn't get called for data that isn't specified, else you could do:
// note: doesn't work; see answer text
[XmlIgnore]
public bool ConflictListSpecified
{
get { return ConflictList != null; }
set { if (!value) ConflictList = null; }
}
Add to that the fact that serialization callbacks aren't supported in XmlSerializer and I'm out of options.
I've tested it back to .NET 2.0, and it behaves the same there too...

XmlSerializer unreliable or am I doing something wrong?

If you run this code:
public class Program
{
public class MyClass
{
public string Text { get; set; }
}
static MyClass myClass = new MyClass();
static string Desktop = "C:\\Users\\Juan Luis\\Desktop\\";
static void Main(string[] args)
{
myClass.Text = "\r\nhello";
Console.WriteLine((int)myClass.Text[0]);
Save(Desktop + "file.xml", myClass);
myClass = Load(Desktop + "file.xml");
Console.WriteLine((int)myClass.Text[0]);
Console.Read();
}
private static MyClass Load(string fileName)
{
MyClass result = null;
using (Stream stream = File.Open(fileName, FileMode.Open))
{
XmlSerializer xmlFormatter = new XmlSerializer(typeof(MyClass));
result = (MyClass)xmlFormatter.Deserialize(stream);
}
return result;
}
private static void Save(string fileName, MyClass obj)
{
using (Stream tempFileStream = File.Create(fileName))
{
XmlSerializer xmlFormatter = new XmlSerializer(typeof(MyClass));
xmlFormatter.Serialize(tempFileStream, obj);
}
}
}
The output will be 13, 10. The XmlSerializer is removing the carriage return. This is a problem in my case because I need to compare strings for equality in a class that gets serialized and deserialized, and this is causing two strings that are equal before serializing to be unequal after serialized. What would be the best work around?
Edit: After reading answers, this was my solution, in case it will help anyone:
public class SafeXmlSerializer : XmlSerializer
{
public SafeXmlSerializer(Type type) : base(type) { }
public new void Serialize(Stream stream, object o)
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.NewLineHandling = NewLineHandling.Entitize;
using (XmlWriter xmlWriter = XmlWriter.Create(stream, ws))
{
base.Serialize(xmlWriter, o);
}
}
}
I wouldn't call it unreliable exactly: the XmlSerializer strips white space around text inside elements. If it didn't do this then the meaning of XML documents would change according to how you formatted them in the IDE.
You could consider putting the text in a CDATA section, which will preserve the contents exactly. For example, How do you serialize a string as CDATA using XmlSerializer?
Edit: This looks to have a better explanation of where the problem lies, along with a simpler solution - How to keep XmlSerializer from killing NewLines in Strings?

Can I make XmlSerializer ignore the namespace on deserialization?

Can I make XmlSerializer ignore the namespace (xmlns attribute) on deserialization so that it doesn't matter if the attribute is added or not or even if the attribute is bogus? I know that the source will always be trusted so I don't care about the xmlns attribute.
Yes, you can tell the XmlSerializer to ignore namespaces during de-serialization.
Define an XmlTextReader that ignores namespaces. Like so:
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader): base(reader) { }
public override string NamespaceURI
{
get { return ""; }
}
}
// helper class to omit XML decl at start of document when serializing
public class XTWFND : XmlTextWriter {
public XTWFND (System.IO.TextWriter w) : base(w) { Formatting= System.Xml.Formatting.Indented;}
public override void WriteStartDocument () { }
}
Here's an example of how you would de-serialize using that TextReader:
public class MyType1
{
public string Label
{
set { _Label= value; }
get { return _Label; }
}
private int _Epoch;
public int Epoch
{
set { _Epoch= value; }
get { return _Epoch; }
}
}
String RawXml_WithNamespaces = #"
<MyType1 xmlns='urn:booboo-dee-doo'>
<Label>This document has namespaces on its elements</Label>
<Epoch xmlns='urn:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'>0</Epoch>
</MyType1>";
System.IO.StringReader sr;
sr= new System.IO.StringReader(RawXml_WithNamespaces);
var s1 = new XmlSerializer(typeof(MyType1));
var o1= (MyType1) s1.Deserialize(new NamespaceIgnorantXmlTextReader(sr));
System.Console.WriteLine("\n\nDe-serialized, then serialized again:\n");
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("urn", "booboo-dee-doo");
s1.Serialize(new XTWFND(System.Console.Out), o1, ns);
Console.WriteLine("\n\n");
The result is like so:
<MyType1>
<Label>This document has namespaces on its elements</Label>
<Epoch>0</Epoch>
</MyType1>
If you expect no namespace, but the input has namespaces, then you can set
Namespaces = false
on your XmlTextReader.
Exdended Wolfgang Grinfeld answer (w/o exception handling):
public static Message Convert(XmlDocument doc)
{
Message obj;
using (TextReader textReader = new StringReader(doc.OuterXml))
{
using (XmlTextReader reader = new XmlTextReader(textReader))
{
reader.Namespaces = false;
XmlSerializer serializer = new XmlSerializer(typeof(Message));
obj = (Message)serializer.Deserialize(reader);
}
}
return obj;
}
Solved this by using XmlSerializer Deserialize to read from xml instead from stream. This way before xml is Deserialized, using Regex to remove xsi:type from the xml. Was doing this is Portable Class Library for Cross Platform, so did not had many other options :(. After this the deserialization seems to work fine.
Following code can help,
public static TClass Deserialize<TClass>(string xml) where TClass : class, new()
{
var tClass = new TClass();
xml = RemoveTypeTagFromXml(xml);
var xmlSerializer = new XmlSerializer(typeof(TClass));
using (TextReader textReader = new StringReader(xml))
{
tClass = (TClass)xmlSerializer.Deserialize(textReader);
}
return tClass;
}
public static string RemoveTypeTagFromXml(string xml)
{
if (!string.IsNullOrEmpty(xml) && xml.Contains("xsi:type"))
{
xml = Regex.Replace(xml, #"\s+xsi:type=""\w+""", "");
}
return xml;
}
Why try to make the XmlSerializer forget how XML works? It's a fact of XML that two elements with the same name but different namespaces are different elements.
If you want to process XML that has no namespaces, then you should pre-process it to remove the namespaces, and then pass it to the serializer.

Categories

Resources