Serializing to XML via DataContract: custom output? - c#

I have a custom Fraction class, which I'm using throughout my whole project. It's simple, it consists of a single constructor, accepts two ints and stores them. I'd like to use the DataContractSerializer to serialize my objects used in my project, some of which include Fractions as fields. Ideally, I'd like to be able to serialize such objects like this:
<Object>
...
<Frac>1/2</Frac> // "1/2" would get converted back into a Fraction on deserialization.
...
</Object>
As opposed to this:
<Object>
...
<Frac>
<Numerator>1</Numerator>
<Denominator>2</Denominator>
</Frac>
...
</Object>
Is there any way to do this using DataContracts?
I'd like to do this because I plan on making the XML files user-editable (I'm using them as input for a music game, and they act as notecharts, essentially), and want to keep the notation as terse as possible for the end user, so they won't need to deal with as many walls of text.
EDIT: I should also note that I currently have my Fraction class as immutable (all fields are readonly), so being able to change the state of an existing Fraction wouldn't be possible. Returning a new Fraction object would be OK, though.

If you add a property that represents the Frac element and apply the DataMember attribute to it rather than the other properties you will get what you want I believe:
[DataContract]
public class MyObject {
Int32 _Numerator;
Int32 _Denominator;
public MyObject(Int32 numerator, Int32 denominator) {
_Numerator = numerator;
_Denominator = denominator;
}
public Int32 Numerator {
get { return _Numerator; }
set { _Numerator = value; }
}
public Int32 Denominator {
get { return _Denominator; }
set { _Denominator = value; }
}
[DataMember(Name="Frac")]
public String Fraction {
get { return _Numerator + "/" + _Denominator; }
set {
String[] parts = value.Split(new char[] { '/' });
_Numerator = Int32.Parse(parts[0]);
_Denominator = Int32.Parse(parts[1]);
}
}
}

DataContractSerializer will use a custom IXmlSerializable if it is provided in place of a DataContractAttribute. This will allow you to customize the XML formatting in anyway you need... but you will have to hand code the serialization and deserialization process for your class.
public class Fraction: IXmlSerializable
{
private Fraction()
{
}
public Fraction(int numerator, int denominator)
{
this.Numerator = numerator;
this.Denominator = denominator;
}
public int Numerator { get; private set; }
public int Denominator { get; private set; }
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
var content = reader.ReadInnerXml();
var parts = content.Split('/');
Numerator = int.Parse(parts[0]);
Denominator = int.Parse(parts[1]);
}
public void WriteXml(XmlWriter writer)
{
writer.WriteRaw(this.ToString());
}
public override string ToString()
{
return string.Format("{0}/{1}", Numerator, Denominator);
}
}
[DataContract(Name = "Object", Namespace="")]
public class MyObject
{
[DataMember]
public Fraction Frac { get; set; }
}
class Program
{
static void Main(string[] args)
{
var myobject = new MyObject
{
Frac = new Fraction(1, 2)
};
var dcs = new DataContractSerializer(typeof(MyObject));
string xml = null;
using (var ms = new MemoryStream())
{
dcs.WriteObject(ms, myobject);
xml = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(xml);
// <Object><Frac>1/2</Frac></Object>
}
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
ms.Position = 0;
var obj = dcs.ReadObject(ms) as MyObject;
Console.WriteLine(obj.Frac);
// 1/2
}
}
}

This MSDN article describes IDataContractSurrogate Interface which:
Provides the methods needed to substitute one type for another by the
DataContractSerializer during serialization, deserialization, and
export and import of XML schema documents.
Although way too late, still may help someone. Actually, allows to change XML for ANY class.

You can do this with the DataContractSerializer, albeit in a way that feels hacky to me. You can take advantage of the fact that data members can be private variables, and use a private string as your serialized member. The data contract serializer will also execute methods at certain points in the process that are marked with [On(De)Serializ(ed|ing)] attributes - inside of those, you can control how the int fields are mapped to the string, and vice-versa. The downside is that you lose the automatic serialization magic of the DataContractSerializer on your class, and now have more logic to maintain.
Anyways, here's what I would do:
[DataContract]
public class Fraction
{
[DataMember(Name = "Frac")]
private string serialized;
public int Numerator { get; private set; }
public int Denominator { get; private set; }
[OnSerializing]
public void OnSerializing(StreamingContext context)
{
// This gets called just before the DataContractSerializer begins.
serialized = Numerator.ToString() + "/" + Denominator.ToString();
}
[OnDeserialized]
public void OnDeserialized(StreamingContext context)
{
// This gets called after the DataContractSerializer finishes its work
var nums = serialized.Split("/");
Numerator = int.Parse(nums[0]);
Denominator = int.Parse(nums[1]);
}
}

You'll have to switch back to the XMLSerializer to do that. The DataContractSerializer is a bit more restrictive in terms of being able to customise the output.

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

Cannot deserialize XML into a list using XML Deserializer

This follows on from my previous question Serialize list of interfaces using XML Serialization
public class MeterWalkOrder
{
public MeterWalkOrder()
{
Meters = new List<IMeter>();
}
public String Name { get; set; }
[XmlIgnore]
public List<IMeter> Meters { get; set; }
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public List<Meter> SerializableMeters
{
get
{
return Meters.Cast<Meter>().ToList();
}
set
{
Meters = new List<IMeter>(value);
}
}
}
public interface IMeter {
int MeterID { get; set; }
}
public class Meter : IMeter {
public int MeterID { get; set; }
public string SerialNumber { get; set; }
}
}
I am using the extension method below to deserialize the XML back into my object (ideally I would prefer the extension method to be off of object, but I not too comfortable with extension methods so I have left like this for now)...
public static class SerializationExtensions
{
public static T LoadFromXML<T>(this string xmlString)
{
T returnValue = default(T);
XmlSerializer serial = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(xmlString);
object result = serial.Deserialize(reader);
if (result != null && result is T)
{
returnValue = ((T)result);
}
reader.Close();
return returnValue;
}
....However, when I give the XML below....
<?xml version="1.0"?>
<MeterWalkOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Red Route</Name>
<Meters>
<Meter>
<MeterID>1</MeterID>
<SerialNumber>12345</SerialNumber>
</Meter>
<Meter>
<MeterID>2</MeterID>
<SerialNumber>SE</SerialNumber>
</Meter>
</Meters>
</MeterWalkOrder>
No meters are populated?
Does anyone know what could cause this problem? The XML is valid and SerializeableMeters is simply a property that reads from and writes to Meters but casting it as a concrete class due to the known issues with using interfaces in serialization
The problem is that XmlSerializer deserializes a property referring to a class implementing IList<T> in the following way:
It calls the getter to get the list. If null, it allocates a list and sets it via the setter. It holds onto the list in some local variable while reading it.
It deserializes each list element, and adds it to the list it is holding.
And that's it. It never calls the containing class's list property setter afterwards.
You can verify this by replacing your List<Meter> with an ObservableCollection<Meter>, and setting a debug listener for when the collection changes:
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public ObservableCollection<Meter> SerializableMeters
{
get
{
Debug.WriteLine("Returning proxy SerializableMeters");
var list = new ObservableCollection<Meter>(Meters.Cast<Meter>());
list.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(list_CollectionChanged);
return list;
}
set
{
Debug.WriteLine("Setting proxy SerializableMeters");
Meters = new List<IMeter>(value.Cast<IMeter>());
}
}
static void list_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var collection = (IList<Meter>)sender;
Debug.WriteLine("Proxy collection changed to include : ");
foreach (var item in collection)
Debug.WriteLine(" " + item.ToString());
}
Doing so, you'll see the following debug output:
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Proxy collection changed to include :
Meter: 1, 12345
Proxy collection changed to include :
Meter: 1, 12345
Meter: 2, SE
As you can see, the list is never set back.
Luckily, there's an easy alternative. If you return a proxy array instead of a proxy List, XmlSerializer will allocate the array itself, populate it, and set it via the setter -- which is just what you want!
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public Meter [] SerializableMeters
{
get
{
return Meters.Cast<Meter>().ToArray();
}
set
{
Meters = new List<IMeter>(value.Cast<IMeter>());
}
}
And then later
var meters = xml.LoadFromXML<MeterWalkOrder>();
Debug.Assert(meters.Meters.Count == 2); // No assert.

How to make XmlSerializer.Deserialize handle DefaultAttribute properly?

There seems to be a bug / inconsistency in the Microsoft XmlSerializer: If you have a property marked with a System.ComponentModel.DefaultValue attribute, this does not get serialized. Fair enough - this could be seen as an expected behavior.
The problem is the same attribute is not respected when deserializing. The code below illustrates the issue.
Question is how could I bypass this? I have potentially hundreds of business classes with default values used in the UI tier (Views), so default value initialization in constructor is not an option. It has to be something generic. I could create a completely new default attribute, but it seems like duplicate work. Do you see a way to override the XmlSerializer behavior or should I use just another serializer that does the job better?
The example code:
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
[TestMethod]
public void SimpleDefaultValueTest()
{
// Create object and set the property value TO THE DEFAULT
var before = new DefaultValueTestClass();
before.Foo = 10000;
// Serialize => xml
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(DefaultValueTestClass));
string xml;
using (var stream = new System.IO.StringWriter())
{
serializer.Serialize(stream, before);
xml = stream.ToString();
}
// Deserialize the same object
DefaultValueTestClass after;
using (var reader = new System.IO.StringReader(xml))
{
after = (DefaultValueTestClass)serializer.Deserialize(reader);
}
// before.Foo = 10000
// after.Foo = 0
Assert.AreEqual(before.Foo, after.Foo);
}
It is your job to implement the defaults; [DefaultValue] merely says "this is the default, you don't need to worry about this" - it doesn't apply it. This applies not just to XmlSerializer, but to the core System.ComponentModel API to which [DefaultValue] belongs (which drives things like the bold / not-bold in PropertyGrid, etc)
Basically:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
[DefaultValue(10000)]
public int Foo { get; set; }
}
will work in the way you expect. If you want it to serialize whether or not it is that particular value, then the correct implementations is:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
public int Foo { get; set; }
}
If you want to preserve the [DefaultValue], but want it to always serialize, then:
public class DefaultValueTestClass
{
[DefaultValue(10000)]
public int Foo { get; set; }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeFoo() { return true; }
}
Where ShouldSerialize* is another pattern from System.ComponentModel that is recognised by several serializers.
And here's some UI code to show that XmlSerializer is actually doing exactly the same things that the UI code (built on System.ComponentModel) has always done:
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[System.STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var form = new Form())
using (var grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
var obj = new DefaultValueTestClass
{ // TODO - try with other numbers to
// see bold / not bold
Foo = 10000
};
// note in the grid the value is shown not-bold; that is
// because System.ComponentModel is saying
// "this property doesn't need to be serialized"
// - or to show it more explicitly:
var prop = TypeDescriptor.GetProperties(obj)["Foo"];
bool shouldSerialize = prop.ShouldSerializeValue(obj);
// ^^^ false, because of the DefaultValueAttribute
form.Text = shouldSerialize.ToString(); // win title
grid.SelectedObject = obj;
form.Controls.Add(grid);
Application.Run(form);
}
}
}
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}

Avoid expanding linked objects when serialising

I am using JSON.NET to serialize some c# objects into JSON (and then write to a file).
My two main classes are:
public class Reservoir {
private Well[] mWells;
public Well[] wells {
get { return mWells; }
set { mWells = value; }
}
}
and
public Well() {
private string mWellName;
private double mY;
private double mX;
public string wellName {
get { return mWellName; }
set { mWellName = value; }
}
public double y {
get { return mY; }
set { mY = value; }
}
public double x {
get { return mX; }
set { mX = value; }
}
private Well[] mWellCorrelations;
}
The problem is that the output looks like:
'{"wells":[{"wellName":"B-B10","y":217.04646503367468,"x":469.5776343820333,"wellCorrelations":[{"wellName":"B-B12","y":152.71005958395972,"x":459.02158140110026,"wellCorrelations":[{"wellName":"B-B13","y":475.0,"x":495.14804408905263,"wellCorrelations":[{"wellName":"B-B11","y":25.0,"x":50.0,"wellCorrelations":[]}
i.e. the associated wells of each well object are expanded as objects themselves and this becomes a serious problem of space and time when there lots of associated objects.
I suppose I would have preferred something like:
'{"wells":[{"wellName":"B-B10","y":217.04646503367468,"x":469.5776343820333,"wellCorrelations":[{"wellName":"B-B12"}], {"wellName":"B-B11","y":217.04646503367468,"x":469.5776343820333,"wellCorrelations":[{"wellName":"B-B13"}
i.e maintaining only the well name as the link (assume its unique).
Is there a way to do this with JSON.NET?
You have set
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
but it doesn't make any difference.
You could add a new readonly property called WellCorrelations that only got the names of the well correlations, and slap a JsonIngore attribute on your mWellCorrelations, like so:
[JsonIgnore]
private Well[] mWellCorrelations;
public string[] WellCorrelations
{
get { return mWellCorrelations.Select(w => w.wellName).ToArray(); }
}
http://james.newtonking.com/projects/json/help/html/ReducingSerializedJSONSize.htm
That way, the serializer will only serialize the names of the correlated wells.

C# Serialize a class with a List data member

I have this c# class:
public class Test
{
public Test() { }
public IList<int> list = new List<int>();
}
Then I have this code:
Test t = new Test();
t.list.Add(1);
t.list.Add(2);
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
StringWriter sw = new StringWriter();
XmlSerializer xml = new XmlSerializer(t.GetType());
xml.Serialize(sw, t);
When I look at the output from sw, its this:
<?xml version="1.0" encoding="utf-16"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
the values 1,2 I added to the list member variable dont show up.
So how can I fix this ? I made the list a property but it still doesnt seem to work.
I am using xml serialization here, are there any other serializers ?
I want performance! Is this the best approach ?
--------------- UPDATE BELOW -------------------------
So the actual class I want to serialize is this:
public class RoutingResult
{
public float lengthInMeters { get; set; }
public float durationInSeconds { get; set; }
public string Name { get; set; }
public double travelTime
{
get
{
TimeSpan timeSpan = TimeSpan.FromSeconds(durationInSeconds);
return timeSpan.TotalMinutes;
}
}
public float totalWalkingDistance
{
get
{
float totalWalkingLengthInMeters = 0;
foreach (RoutingLeg leg in Legs)
{
if (leg.type == RoutingLeg.TransportType.Walk)
{
totalWalkingLengthInMeters += leg.lengthInMeters;
}
}
return (float)(totalWalkingLengthInMeters / 1000);
}
}
public IList<RoutingLeg> Legs { get; set; } // this is a property! isnit it?
public IList<int> test{get;set;} // test ...
public RoutingResult()
{
Legs = new List<RoutingLeg>();
test = new List<int>(); //test
test.Add(1);
test.Add(2);
Name = new Random().Next().ToString(); // for test
}
}
But the XML produced by the serializer is this:
<RoutingResult>
<lengthInMeters>9800.118</lengthInMeters>
<durationInSeconds>1440</durationInSeconds>
<Name>630104750</Name>
</RoutingResult>
???
its ignoring both of those lists ?
1) Your list is a field, not a property, and the XmlSerializer will only work with properties, try this:
public class Test
{
public Test() { IntList = new List<int>() }
public IList<int> IntList { get; set; }
}
2) There are other Serialiation options, Binary the main other one, though there is one for JSON as well.
3) Binary is probably the most performant way, since it is typically a straight memory dump, and the output file will be the smallest.
list is not a Property. Change it to a publicly visible property, and it should be picked up.
I figured it out that XmlSerializer doesnt work if I use IList so I changed it to List, that made it work. As Nate also mentioned.

Categories

Resources