简体   繁体   中英

Need Parse XML to List<Object>, name field are separated from data

I am implementing a windows service and i need to consume a WebService with REST, I want to parse this to List but i dont know how. My problem is that the names of the fields are separated from the data.

The structure I get is this:

  <?xml version="1.0" encoding="utf-8" ?> 
<xml>
  <result>OK</result> 
<headers>
  <header>lastname</header> 
  <header>firstname</header> 
  <header>Age</header> 
  </headers>
<data>
<datum>
  <item>Kelly</item> 
  <item>Grace</item> 
  <item>33</item> 
</datum>
  </data>
</xml>

You can use XmlSerializer to deserialize that XML into c# classes that reflect its structure. For instance:

[XmlRoot("xml")]  // Indicates that the root element is named "xml"
public class XmlResponse
{
    [XmlElement("result")] // Indicates that this element is named "result"
    public string Result { get; set; }

    [XmlArray("headers")]  // Indicates two-level list with outer element named "headers" and inner elements named "header"
    [XmlArrayItem("header")]
    public List<string> Headers { get; set; }

    [XmlArray("data")] // Indicates two-level list with outer element named "data" and inner elements named "datum"
    [XmlArrayItem("datum")]
    public List<XmlResponseDatum> Data { get; set; }
}

public class XmlResponseDatum
{
    [XmlElement("item")] // Indicates a one-level list with repeated elements named "item".
    public List<string> Items { get; set; }
}

Which you could deserialize like:

    public static T LoadFromXML<T>(string xmlString)
    {
        using (StringReader reader = new StringReader(xmlString))
        {
            object result = new XmlSerializer(typeof(T)).Deserialize(reader);
            if (result is T)
            {
                return (T)result;
            }
        }
        return default(T);
    }

    public static string GetXml<T>(T obj)
    {
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true, IndentChars = "    " }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj);
            return textWriter.ToString();
        }
    }

    public static void Test()
    {
        string xml = @"<?xml version=""1.0"" encoding=""utf-8"" ?> 
            <xml>
              <result>OK</result> 
            <headers>
              <header>lastname</header> 
              <header>firstname</header> 
              <header>Age</header> 
              </headers>
            <data>
            <datum>
              <item>Kelly</item> 
              <item>Grace</item> 
              <item>33</item> 
            </datum>
              </data>
            </xml>";
        var response = LoadFromXML<XmlResponse>(xml);
        Debug.WriteLine(GetXml(response));

As an alternative, here is some XDocument and using objects that do not need to be Xml decorated.

I advocate this approach because it is easier to tweak the XDocument/Xpath statements in case the structure of the Xml changes.....as opposed to the XmlAttributes.

This also allows the same objects to be used, even if there are different xml streams hydrating it. You just write a different XDocument "shredder" for each Xml Input.

[DebuggerDisplay("MyNotUsedStringKey = {MyNotUsedStringKey}")]
public class ImportParent
{
    public ImportParent()
    {
        this.MyNotUsedStringKey = Guid.NewGuid().ToString("N");
        this.ImportChildren = new ImportChildCollection();
    }

    public ImportChildCollection ImportChildren { get; set; }
    public string MyNotUsedStringKey { get; set; }

}

public class ImportChildCollection : List<ImportChild>
{
    public ImportChildCollection() { }
    public ImportChildCollection(IEnumerable<ImportChild> src)
    {
        if (null != src)
        {
            foreach (ImportChild item in src)
            {
                item.Ordinal = this.Count + 1;
                base.Add(item);
            }
        }

        //AddRange(src);
    }
}

[DebuggerDisplay("MyStringKey = {MyStringKey}, MyStringValue='{MyStringValue}', Ordinal='{Ordinal}'")]
public class ImportChild
{
    public ImportChild()
    {
    }

    public int Ordinal { get; set; }
    public string MyStringKey { get; set; }
    public string MyStringValue { get; set; }
}




            string xmlString = @"<?xml version=""1.0"" encoding=""utf-8"" ?> 
            <xml>
                <result>OK</result> 
                <headers>
                    <header>lastname</header>
                    <header>firstname</header>
                    <header>Age</header>
                </headers>
                <data>
                    <datum>
                        <item>Kelly</item>
                        <item>Grace</item>
                        <item>33</item>
                    </datum>
                </data>
            </xml>           ";

            XDocument xDoc = XDocument.Parse(xmlString);

            //XNamespace ns = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
            string ns = string.Empty;




            List<ImportParent> parentKeys = new List<ImportParent>
            (
                from list in xDoc.Descendants(ns + "xml")
                from item1 in list.Elements(ns + "headers")
                where item1 != null
                select new ImportParent
                {
                    //note that the cast is simpler to write than the null check in your code
                    //http://msdn.microsoft.com/en-us/library/bb387049.aspx
                    ImportChildren = new ImportChildCollection
                    (
                        from detail in item1.Descendants("header")
                        select new ImportChild
                        {
                            MyStringKey = detail == null ? string.Empty : detail.Value
                        }
                    )
                }
            );


            List<ImportParent> parentValues = new List<ImportParent>
            (
                from list in xDoc.Descendants(ns + "xml")
                from item1 in list.Elements(ns + "data")
                from item2 in item1.Elements(ns + "datum")
                where item1 != null && item2 != null
                select new ImportParent
                {
                    //note that the cast is simpler to write than the null check in your code
                    //http://msdn.microsoft.com/en-us/library/bb387049.aspx
                    ImportChildren = new ImportChildCollection
                        (
                            from detail in item1.Descendants("item")
                            select new ImportChild
                            {
                                MyStringValue = detail == null ? string.Empty : detail.Value
                            }
                        )
                }
            );

            /*Match up the Keys to the Values using "Ordinal" matches*/
            foreach (ImportParent parent in parentKeys)
            {
                foreach (ImportChild child in parent.ImportChildren)
                {
                    ImportChild foundMatch = parentValues.SelectMany(x => x.ImportChildren).Where(c => c.Ordinal == child.Ordinal).FirstOrDefault();
                    if (null != foundMatch)
                    {
                        child.MyStringValue = foundMatch.MyStringValue;
                    }
                }
            }

            foreach (ImportParent parent in parentKeys)
            {
                foreach (ImportChild child in parent.ImportChildren)
                {
                    Console.WriteLine("Key={0}, Value={1}", child.MyStringKey, child.MyStringValue);
                }
            }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM