JakeL
JakeL

Reputation: 411

Implementing Custom XML Serialization/Deserialization of compound data type?

In our application, we have a Money type, which contains an amount (decimal) and a currency code (string). In simple form, it looks like this:

public class Money
{
  public decimal Amount{get;set;}
  public string CurrencyCode{get;set;}
}

As you might imagine, this gets used in many places in the application, and is frequently serialized/deserialized as it is sent to/from the client. The Money amount is typically serialized as a compound value, e.g. "1.23USD" to represent $1.23. The old client (HTML/JS) would parse that value into its component parts back into the Money type. Money values are sent as element values, and as attribute values, depending on where they are in the app, e.g:

<SomeClass moneyValue="1.23USD" ... />

or

 <SomeClass>
<MoneyValue>1.23USD</MoneyValue>
...
</SomeClass>

I am trying to figure out a way that I can use the built-in C#/.NET Xml Serialization tools to have this same sort of behavior. I looked at implementing ISerializable, but haven't been able to figure out quite the correct way to do so.

Essentially, I want to be able to have the Money amount deserialized by custom logic of my own (that knows how to parse "1.23USD" into a Money amount), and serialize it to the simple string, e.g. "1.23USD"

The end goal would be to able to have the Money amount in a class as either:

[XmlAttribute]
public Money SomeField // SomeField='123.USD

or:

[XmlElement]
public Money SomeOtherField //<SomeOtherField>1.23USD</SomeOtherField>

just the same way you can do that with simple types like int, string, double, etc.

Is this possible?

Upvotes: 11

Views: 22272

Answers (2)

JakeL
JakeL

Reputation: 411

Turns out there really wasn't a good 100% answer to this - the problem is that you can't serialize the complex type (e.g. Money) to an attribute, even if you serialize it to a simple string. So no matter what, I can't have someAttribute='1.23 USD' that is reusable in a general way.

So...the posted answer is a nice workaround for Attributes, and for places where I used Money as an element, I just implemented IXmlSerializable to output the compound field and re-split it on parse. This way, no wrappers are required, it just works.

public struct Money : IXmlSerializable
    {
        public double Amount { get; set; }
        public string CurrencyCode { get; set; }

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return (null);
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            reader.MoveToContent();

            Boolean isEmptyElement = reader.IsEmptyElement; // (1)
            reader.ReadStartElement();
            if (!isEmptyElement) // (1)
            {
                var str = reader.ReadContentAsString();
                string[] sa = str.Split(' ');
                Amount = double.Parse(sa[0]);
                CurrencyCode = sa[1];
                reader.ReadEndElement();
            }
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteString(Amount + " " + CurrencyCode);
        }
    }

And for attributes, the posted answer works as well, thank you.

Upvotes: 10

Baldrick
Baldrick

Reputation: 11840

The following code does the trick:

public class Money
{
    public decimal Amount { get; set; }
    public string CurrencyCode { get; set; }
}

public class SomeClass
{
    public SomeClass() { }

    [XmlIgnore]
    public Money WrappedMoney { get; set; }

    [XmlAttribute]
    public string moneyValue
    {
        get
        {
            return String.Format("{0:.##}{1}", WrappedMoney.Amount, WrappedMoney.CurrencyCode);
        }
    }

}

public class ParentClass
{
    public SomeClass SomeClass {get; set;}
}

class Program
{
    public static int Main(string[] args)
    {
        var parent = new ParentClass
        {
            SomeClass = new SomeClass
            {
                WrappedMoney = new Money { Amount = 1.25M, CurrencyCode = "USD" }
            }
        };

        var serializer = new XmlSerializer(typeof(ParentClass));

        using (var writer = new StreamWriter("output.xml"))
        {
            serializer.Serialize(writer, parent);
        }

        return 0;
    }
}

Xml output is:

<?xml version="1.0" encoding="utf-8"?>
<ParentClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SomeClass moneyValue="1.25USD" />
</ParentClass>

Upvotes: 3

Related Questions