LukeHennerley
LukeHennerley

Reputation: 6444

Set values of a class based on XML strucutre

I want to process certain elements of an XML string into an object based on the properties in the object matching up to the names of elements in the XML.

An example structure of the XML is as follows:

<Bar>
  <Body>
    <Header>
      <A>Value</A>
      <B>true</B>
    </Header> 
    <Data>
      <D>Value</D>
    </Data>
    <Data>
      <D>Value2</D>
    <Data>  
  </Body>
</Bar>

There can be MANY <Data> elements in the XML, however <Header> only exists once. The class I have set up is as so:

public class Foo
{
  public string A { get; set; }
  public bool B { get; set; }
  public List<FooData> { get; set; }
  public void ProcessXml(string xml)
  {
    XDocument xDoc = XDocument.Load(new StringReader(xml));
    var propVals = (from ele in xDoc.Descendants()
                    join prop in this.GetType().GetProperties() on ele.Name.LocalName equals prop.Name
                    select new
                    {
                      prop = prop,
                      val = new Func<object>(() =>
                        {
                          object objValue = null;
                          prop.PropertyType.TryParse(ele.Value, ref objValue);
                          return objValue;
                        }).Invoke()
                    });
    propVals.ToList().ForEach(x => x.prop.SetValue(this, x.val, null));
  }
}
public class FooData
{
  public string D { get; set; }
}

I came up with the method ProcessXml which starts to set things up, however at the minute I am only ever setting Header values (A, B), any ideas on how I can add many FooData items into the List from within the same method easily?

public static class TypeExtensions
{
  public static void TryParse(this Type t, object valIn, ref object valOut)
  {
    //Do some parsing logic
    try{
      out = TypeDescriptor.GetConverter(t).ConvertFromInvariantString(valIn);
      return true;
    } catch(Exception) { return false; }
  }
}

Upvotes: 1

Views: 77

Answers (2)

Dead.Rabit
Dead.Rabit

Reputation: 1975

If your XML is really that simple, you can use Serialization to get this dynamic/self loading behavior, by decorating your model classes with the appropriate XML Serialization attributes though you will need at least 1 class for each level of indentation in the document:

void Main()
{
    var xml = @"<Bar>
  <Body>
    <Header>
      <A>Value</A>
      <B>true</B>
    </Header> 
    <Data>
      <D>Value</D>
    </Data>
    <Data>
      <D>Value2</D>
    </Data>  
  </Body>
 </Bar>";

    var serializer = new XmlSerializer(typeof(Bar));
    serializer.Deserialize( new MemoryStream( Encoding.ASCII.GetBytes( xml ) ) ).Dump();
}

public class Bar
{
    public Body Body { get; set; }
}

public class Body
{
    public Header Header { get; set; }

    [XmlElement]
    public Data[] Data { get; set; }
}

public class Header
{
    public string A { get; set; }
    public string B { get; set; }
}

public class Data
{
    public string D { get; set; }
}

(Edited: I missed that there's only 1 Header element)

Upvotes: 0

LukeHennerley
LukeHennerley

Reputation: 6444

I went along a similar line as what I did with the header stuff as there isn't an easy way to combine this into one line.

var dataElements = (from dataDescendants in (from ele2 in xDoc.Descendants()
                                             Where ele2.Name.LocalName == "Data"
                                             select ele2.Descendants())
                    from dataDescendant in dataDescendants
                    join prop in typeof(FooItem).GetProperties() on prop.Name equals dataDescendant.Name.LocalName
                    select Element = dataDescendant, Property = prop, dataDescendants
                    group by dataDescendants into g = group
                    select g).ToList();
dataElements.ForEach(dataElement =>
                     {
                       FooItem fi = new FooItem();
                       dataElement.ToList.ForEach(x =>
                                                  {
                                                    object objVal = null;
                                                    x.Property.PropertyType.TryParse(x.Element.Value, objVal);
                                                    x.Property.SetValue(fi, objVal, null);
                                                  }
                       DataItems.Add(fi);
                     }

Upvotes: 1

Related Questions