Deserialize key/value XML data to C# object

Viewed 183

I need deserialize XML file "c:\Temp\Des.xml":

<return xsi:type="ns2:Map"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <item>
        <key xsi:type="xsd:int">218980</key>
        <value xsi:type="ns2:Map">
            <item>
                <key xsi:type="xsd:string">id</key>
                <value xsi:type="xsd:string">218980</value>
            </item>
            <item>
                <key xsi:type="xsd:string">title</key>
                <value xsi:type="xsd:string">Product Title 1</value>
            </item>
            <item>
                <key xsi:type="xsd:string">price</key>
                <value xsi:type="xsd:string">10.30</value>
            </item>
            <item>
                <key xsi:type="xsd:string">images</key>
                <value xsi:type="ns2:Map">
                    <item>
                        <key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
                        <value xsi:type="xsd:string">https://test.com/Image1.jpg</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
                        <value xsi:type="xsd:string">https://test.com/Image2.jpg</value>
                    </item>
                </value>
            </item>
            <item>
                <key xsi:type="xsd:string">specifications</key>
                <value SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array"
                    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
                    <item xsi:type="ns2:Map">
                        <item>
                            <key xsi:type="xsd:string">name</key>
                            <value xsi:type="xsd:string">name1</value>
                        </item>
                        <item>
                            <key xsi:type="xsd:string">value</key>
                            <value xsi:type="xsd:string">value1</value>
                        </item>
                    </item>
                    <item xsi:type="ns2:Map">
                        <item>
                            <key xsi:type="xsd:string">name</key>
                            <value xsi:type="xsd:string">name2</value>
                        </item>
                        <item>
                            <key xsi:type="xsd:string">value</key>
                            <value xsi:type="xsd:string">value2</value>
                        </item>
                    </item>
                </value>
            </item>
        </value>
    </item>

    <item>
        <key xsi:type="xsd:int">218981</key>
        <value xsi:type="ns2:Map">
            <item>
                <key xsi:type="xsd:string">id</key>
                <value xsi:type="xsd:string">218981</value>
            </item>
            <item>
                <key xsi:type="xsd:string">title</key>
                <value xsi:type="xsd:string">Product Title 2</value>
            </item>
            <item>
                <key xsi:type="xsd:string">price</key>
                <value xsi:type="xsd:string">10.40</value>
            </item>
            <item>
                <key xsi:type="xsd:string">images</key>
                <value xsi:type="ns2:Map">
                    <item>
                        <key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
                        <value xsi:type="xsd:string">https://test.com/Image4.jpg</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
                        <value xsi:type="xsd:string">https://test.com/Image5.jpg</value>
                    </item>
                </value>
            </item>
            <item>
                <key xsi:type="xsd:string">specifications</key>
                <value SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array"
                    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
                    <item xsi:type="ns2:Map">
                        <item>
                            <key xsi:type="xsd:string">name</key>
                            <value xsi:type="xsd:string">name12</value>
                        </item>
                        <item>
                            <key xsi:type="xsd:string">value</key>
                            <value xsi:type="xsd:string">value12</value>
                        </item>
                    </item>
                    <item xsi:type="ns2:Map">
                        <item>
                            <key xsi:type="xsd:string">name</key>
                            <value xsi:type="xsd:string">name22</value>
                        </item>
                        <item>
                            <key xsi:type="xsd:string">value</key>
                            <value xsi:type="xsd:string">value22</value>
                        </item>
                    </item>
                </value>
            </item>
        </value>
    </item>
</return>

I created class for that

[Serializable()]
[XmlRoot("Item", Namespace = "", IsNullable = false)]
public class Item
{
    [XmlElement("id")]
    public int Id { get; set; }

    [XmlElement("title")]
    public string Title { get; set; }
    
    [XmlElement("price")]
    public decimal Price { get; set; }
    
    [XmlElement("images")]
    public string[] Images { get; set; }
    
    [XmlElement("specifications")]
    public object Specifications { get; set; }
}

and call Deserialize:

public class Program
    {
        static void Main(string[] args)
        {
            string xml = File.ReadAllText("c:\\Temp\\Des.xml");
            StringReader stringReader = new StringReader(xml);

            XmlSerializer serializer = new XmlSerializer(typeof(List<Item>), new XmlRootAttribute("return"));
            List<Item> items = (List<Item>) serializer.Deserialize(stringReader);

but items are always empty list, what I'm doing wrong? Thank you!

2 Answers

As @jle said here:

I don't think attributes will work because of the key/value structure. There is no way for a program to infer from the XML alone what properties an object has. I would make a static extension method helper function to get the values:

you can use the below extension method for getting the value of each key:

public static class XmlHelper
    {
        public static string GetValueByKeyName(this XElement element, string key)
        {
            return element.Descendants("key")
                         .First(v => v.Value == key)
                         .ElementsAfterSelf("value")
                         .First()
                         .Value;
        }
    }

and then, your code became like that:

        var ItemObjects = new List<Item>();

        XDocument doc = XDocument.Load(@"D:/file.xml");
        var elements = doc.Root.Elements("item").ToList();
        elements.ForEach(item => {
            ItemObjects.Add(new Item { 
            Id = int.Parse(item.GetValueByKeyName("id")),
            Title = item.GetValueByKeyName("title"),
            Price = decimal.Parse(item.GetValueByKeyName("price"), CultureInfo.InvariantCulture),
            //other props
            });
        });

I test it with your data, and it works properly. good luck.

Ok, I found solution, in case anyone need it, I use Linq to XML for that:

var items = (from element in xdoc.Descendants("return").Elements("item")
                         select new
                         {
                             Id = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "id").Elements("value").First().Value,
                             Title = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "title").Elements("value").First().Value,
                             Price = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "price").Elements("value").First().Value,
                             Images = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "images").Elements("value").Elements("item").Elements("value").Select(i => i.Value).ToList(),
                             Specifications = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "specifications").Elements("value").Elements("item").Select(s => 
                                    new KeyValuePair<string, string>
                                    (
                                        s.Elements("item").First().Elements("value").First().Value,
                                        s.Elements("item").First().Elements("value").Last().Value
                                    )
                                ).ToList(),

                         }
                         ).ToList();
Related