0

When trying a lot of examples of deserialize xml into a object in C#. I manage to get some values of some of the elements. But nested values from recuring nodes Like the ID of level 2 , or the values of level4) are giving me not the values I expected. Do I mis something??

The importing part is not a problem (I think)

The demo xml

<?xml version="1.0"?>
<root>
  <b>value1</b>
  <level1>
    <level2>
      <id>1</id>
      <level3>
        <level4>Value 1.1</level4>
        <level4>Value 1.2</level4>
      </level3>
     </level2>
     <level2>
      <id>2</id>
      <level3>
        <level4>Value 2.1</level4>
        <level4>Value 2.2</level4>
      </level3>
    </level2>
  </level1>
</root>

the objects

[XmlRoot(ElementName = "root")]
public class root 
{
    [XmlElement("b")]
    public string b { get; set; }

    [XmlElement("level1")]
    public level1 level1 { get; set; }
}

public class level1
{
    [XmlElement("level2")]
    public List<level2> level2 { get; set; }
}

public class level2
{
    [XmlElement("id")]
    public int id { get; set; }

    [XmlElement("level3")]
    public level3 level3 { get; set; }
}

public class level3
{
    [XmlElement("level4")]
    public List<string> level4 { get; set; }
    //OR 
    //public string[] level4 { get; set; }
}

A parsehelper

 static class ParseHelpers
 {
    public static Stream ToStream(this string @this)
    {
        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);
        writer.Write(@this);
        writer.Flush();
        stream.Position = 0;
        return stream;
    }

    public static T ParseXML<T>(this string @this) where T : class
    {
        var reader = XmlReader.Create(@this.Trim().ToStream()
           , new XmlReaderSettings() 
           { 
               ConformanceLevel = ConformanceLevel.Document 
           });
        return new XmlSerializer(typeof(T)).Deserialize(reader) as T;
    }
 }

Deserialize

string filepath = @"C:\xml.xml";
string xml = File.ReadAllText(filepath);
var a = xml.ParseXML<root>();

Here some of the things I tried

Console.WriteLine(a.b);
// this is working :
// return
// value1

Console.WriteLine(a.level1.level2);
// expected error 
// System.Collections.Generic.List`1[Project.level2]

Console.WriteLine(a.level1.level2[0].id.ToString());
// System.ArgumentOutOfRangeException: 'Index was out of range.

foreach(var item in (a.level1.level2))
{
    Console.WriteLine(item.id.ToString());
}
// return
// 0
// 0

List<level2> l1 = a.level1.level2;

foreach (var item in (l1))
{
    Console.WriteLine(item.id.ToString());
}
// return
// 0
// 0
Bunkerbuster
  • 963
  • 9
  • 17
  • Your model does not work on conceptual level. For example, a.level1.level2 is first level2 element in array or the second? You can define XmlElement level1 as array of level2 (using XmlArray attribute or not) and address it as array a.level1[0], but of course semantically is not the same :-( – vitalygolub Jan 12 '18 at 11:14
  • do you mean this public class level1 { //[XmlElement("level2")] [System.Xml.Serialization.XmlArrayItemAttribute("level2", IsNullable = true)] public List level2 { get; set; } } ==> not working – Bunkerbuster Jan 12 '18 at 12:11
  • When i checked, i did `[XmlArray("level1")] [XmlArrayItem("level2", Type = typeof(level2))] public level2[] level1 { get; set; }` in root. Element level1 is array of elements level2. Probably, you can use List, not checked. But in this case you lose naming root.level1[0].level3[1] – vitalygolub Jan 12 '18 at 13:06
  • i checked, `public List level1 { get; set; }` made the trick even without attributes, I can access root.level1[1] – vitalygolub Jan 12 '18 at 13:58
  • Thank you for your help. But I do not get the logic? How do I acces the next level(s), just changing the root properties does not change my outcome (now I cannot navigate thru the nested objects @ all). – Bunkerbuster Jan 12 '18 at 15:40
  • After some tinkering @ home, I got the answer I needed, Thanks for your help. – Bunkerbuster Jan 12 '18 at 22:22

2 Answers2

0

Based on @vitalygolub suggestion to change the properties .I used the edit => paste special (paste XML as Classes) for a new setup of objects (i should have done that first). After some cleanup, I got a new set of objects. ( I do not completely gasp the abstract complexity of the generated objects), but it works exactly as I want.

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = true)]
public partial class root
{
    public string b { get; set; }

    [System.Xml.Serialization.XmlArrayItemAttribute("level2", IsNullable = true)]
    public rootLevel2[] level1 { get; set; }
}

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class rootLevel2
{
    public string id { get; set; }

    [System.Xml.Serialization.XmlArrayItemAttribute("level4", IsNullable = true)]
    public string[] level3 { get; set; }
}

Now I can navigate thru the objects.

foreach(var item_l2 in a.level1)
{
    Console.WriteLine(item_l2.id);

    foreach (var Item_l4 in item_l2.level3)
    {
        Console.WriteLine(Item_l4);
    } 
}
Bunkerbuster
  • 963
  • 9
  • 17
-1

Try following :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;


namespace ConsoleApplication19
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            new Level(FILENAME);

        }
    }
    public class Level
    {
        public static Level root = new Level();

        public string name { get; set; }
        public int? id { get; set; }
        public string text { get; set; }
        public List<Level> children { get; set; }

        public Level() { }
        public Level(string filename)
        {
            XDocument doc = XDocument.Load(filename);
            XElement xRoot = doc.Root;

            ParseTree(xRoot, root);

        }

        public void ParseTree(XElement xParent, Level parent)
        {
            parent.id = (int?)xParent.Element("id");
            parent.text = xParent.NextNode  != null ? xParent.Value : "";
            foreach(XElement level in xParent.Elements().Where(x => x.Name.LocalName.StartsWith("level")))
            {
                Level child = new Level();
                if(parent.children == null) parent.children = new List<Level>();
                parent.children.Add(child);
                ParseTree(level, child);
            }
        }


    }

}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • This is not helpfull, can you explain why this would work? – Bunkerbuster Jan 12 '18 at 15:44
  • Your xml multi-level and is a tree structure. So you have to parse the xml into a classes that are a tree to maintain hierarchy. So the code uses recursion to create the tree. You so not want to have each level of classes a different name it will make the rest of you code more complicated. If you need the level you can add as a property to the class rather than have a different name for each class. – jdweng Jan 12 '18 at 17:13