2
votes

I am trying to parse XML that has an element that looks like the following using an XmlSerializer. There are quite a few currency types under the amount element and I'd like to deserialize them into a collection of objects that have a string property holding the currency type and an integer property holding the amount.

Is there any clean way of doing this without having to custom parse the amount. I'd like to just apply the XmlSerializer attributes to my classes and get something that works.

I have no control over the output XML.

<root>
 <property1>a</property1>
 <property1>b</property1>
 <property1>c</property1>
 <amount>
  <EUR type="integer">1000</EUR>
  <USD type="integer">1100</USD>
 </amount>
<root>
4

4 Answers

3
votes

The best way to attack XML deserialization is to start with serialization. To that end, here are some classes with attributes to control XML serialization:

public sealed class root
{
    [XmlElement("property1")]
    public List<string> property1;

    [XmlArrayItem(Type = typeof(EUR))]
    [XmlArrayItem(Type = typeof(USD))]
    public List<amount> amount;
}

public abstract class amount
{
    [XmlAttribute]
    public string type { get; set; }

    [XmlText]
    public string Value { get; set; }
}

public sealed class EUR : amount { }
public sealed class USD : amount { }

Test code is:

        var root = new root { property1 = new List<string>(), amount = new List<amount>() };
        root.property1.AddRange(new[]{ "a", "b", "c"});
        var eur = new EUR { type = "integer", Value = "1000" };
        var usd = new USD { type = "integer", Value = "1100" };
        root.amount.AddRange(new amount[]{ eur, usd});

which generates the following XML:

<?xml version="1.0" encoding="utf-16"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <property1>a</property1>
  <property1>b</property1>
  <property1>c</property1>
  <amount>
    <EUR type="integer">1000</EUR>
    <USD type="integer">1100</USD>
  </amount>
</root>
1
votes

I think your best bet will be to do partial XML parsing, and keep the content of the <amount> element as a collection of XmlElements. You'll still have to parse it manually, but you'll only have to parse that part manually. Example:

[XmlRoot("root")]
public class RecordInfo
{
    [XmlElement("property1")]
    public List<string> Property1;

    [XmlElement("amount")]
    public AmountRawData AmountData;
}

public class AmountRawData
{
    [XmlAnyElement]
    public List<XmlElement> Content;

    public IEnumerable<AmountInfo> Parse()
    {
        foreach (var element in this.Content)
        {
            yield return
                new AmountInfo()
                {
                    Currency = element.LocalName,
                    Type = element.GetAttribute("type"),
                    Amount = element.InnerText,
                };
        }
    }
}

public class AmountInfo
{
    public string Currency;
    public string Type;
    public string Amount;
}

Example usage:

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

var result = (RecordInfo)serializer.Deserialize(dataStream);

foreach (var amount in result.AmountData.Parse())
    Console.WriteLine($"{amount.Currency} {amount.Type} {amount.Amount}");
0
votes

Answered a similar question last week using xml linq:

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);

            var results = doc.Elements().Select(x => new {
                property1 = x.Elements("property1").Select(y => (string)y).ToList(),
                dictCurrency = x.Elements("amount").Elements().GroupBy(y => y.Name.LocalName, z => (int)z)
                   .ToDictionary(y => y.Key, z => z.FirstOrDefault())
            }).FirstOrDefault();

        }
    }
}
0
votes

Here is one other approach using Cinchoo ETL - an open source library, to de-serialize such XML as below

  1. Define POCO object

    [ChoXmlRecordObject]
    public class Root
    {
        [ChoXmlNodeRecordField(XPath = "//property1")]
        public string[] Properties { get; set; }
        [ChoXmlNodeRecordField(XPath = "//amount/*")]
        public double[] Amounts { get; set; }
    }
    
  2. Deserialize it using xml reader object

        var rec = ChoXmlReader.Deserialize<Root>("*** Xml File Path ***").FirstOrDefault();
        Console.WriteLine(rec.Dump());
    

Disclaimer: I'm the author of this library.