2

I've seen several threads (like this one or this one) that show how to convert an XDocument to a List<> of simple objects, like strings. However, I'm struggling with how to do this with a nested object.

Here's what the XML looks like...

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
.... other stuff removed to make reading easier ....
            <ListOfCustomers>
                <Customer>
                    <CustomerName>A TO Z Fubar</CustomerName>
                    <AccountNumber>A TO001</AccountNumber>
                    <BillingAddress>
                        <Address1>11900 W FUBAR AVE</Address1>
                        <Address2/>
                        <City>FUBAR</City>
                        <State>CO</State>
                        <Zip>80215</Zip>
                        <Country>US</Country>
                    </BillingAddress>
                    <ShippingAddress>
                        <Address1>11900 W FUBAR AVE</Address1>
                        <Address2/>
                        <City>FUBAR</City>
                        <State>CO</State>
                        <Zip>80215</Zip>
                        <Country>US</Country>
                    </ShippingAddress>
                </Customer>
                <Customer>....</Customer>
                <Customer>....</Customer>
            </ListOfCustomers>

And from that XML I've created this class which I now need to get a List<> of...

public class DistributorCustomer
{
    public string CustomerName { get; set; }
    public string AccountNumber { get; set; }
    public BillingAddress BillingAddress { get; set; }
    public ShippingAddress ShippingAddress { get; set; }
}


public class BillingAddress
{
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    public string Country { get; set; }
}

public class ShippingAddress
{
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    public string Country { get; set; }
}

I'm doing this inside an Azure Blog Storage Trigger funciton. I've gotten this far:

XDocument xDoc = XDocument.Load(blobFile);
IEnumerable<XElement> customers = xDoc.Descendants("Customer");

Easy enough! customers is indeed the entire IEnumerable of all the Customers in the XML file. Now I just need to go from an

IEnumerable<XElement>

To a

List<DistributorCustomer>

That, I'm not sure how to do. From the other threads, this should be possible with LINQ to XML, and Loop over customers should not be needed.

Casey Crookston
  • 13,016
  • 24
  • 107
  • 193

3 Answers3

1

Parsing XML like this using Linq can be quite powerful, maybe not the best way to do it (it's been a while since I used XML) but here is one way making use of Linq Select to project the elements into your types:

var customers = xDoc.Descendants("Customer")
    .Select(c => new DistributorCustomer
    {
        CustomerName = c.Element("CustomerName").Value,
        AccountNumber = c.Element("AccountNumber").Value,
        BillingAddress = new BillingAddress
        {
            Address1 = c.Element("BillingAddress").Element("Address1").Value,
            // etc...
        }
    });
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • Excellent! Thank you :) Testing this now. – Casey Crookston Jul 30 '19 at 15:25
  • Ok, as is, this gets me an `IEnumerable`. If I add a .ToList() at the end, it throws errors. `Null Object Reference' . But I can figure that out. Probably because not all of the xml elements have values. Thank You! – Casey Crookston Jul 30 '19 at 15:37
  • Yeah, if the elements are missing, you will get `NulReferenceException`s. You can mitigate that with something like this `AccountNumber = c.Element("AccountNumber")?.Value` – DavidG Jul 30 '19 at 15:41
1

Why not try Serializing the xml to a list of the object, it is shorter and cleaner

create the base Object

public class ListOfCustomers
{
    [XmlElement("Customer")]
    public List<DistributorCustomer> Customer { get; set; }
}

then

XmlSerializer ser = new XmlSerializer(typeof(ListOfCustomers));
FileStream myFileStream = new FileStream(/*file path*/); //if you are using a file. use Memory stream for xml string
var thelist =  (ListOfCustomers)ser.Deserialize(myFileStream);
Bosco
  • 1,536
  • 2
  • 13
  • 25
  • Hmm... this looks like fun! I'm going to give this a try and report back. – Casey Crookston Jul 30 '19 at 15:54
  • Ok, so offhand, this throws an error on the last line. Pretty sure it's because the stream contains the entire XML file and not just `'s`. If I can get this to work, it is for sure a much more elegant solution. (Side note: I don't need your second line because the Azure Blob Storage Trigger Function automagicly hands me the entier file content as a stream: `blobFile`.) – Casey Crookston Jul 30 '19 at 16:00
  • Are you using memory stream?, I actually tried with saving the xml as a file and serializing – Bosco Jul 30 '19 at 16:01
  • The Azure Function gives me a `System.IO.Stream` which contains the contents of the blob file. In this case, it happens to be an XML file. The sample I put in the OP is edited for the sake of the post. I can't serialize the entire document into a `List`. I need to narrow it down to JUST the `` nodes. – Casey Crookston Jul 30 '19 at 16:04
  • /\ typos fixed. Refresh – Casey Crookston Jul 30 '19 at 16:05
  • I can easily get an IEnumerable of the node easy enough. I would just need to convert that to a stream: `XDocument xDoc = XDocument.Load(blobFile);` `IEnumerable customers = xDoc.Descendants("Customer");` – Casey Crookston Jul 30 '19 at 16:18
1

OP here. I'm going to leave the answer by DavidG as the correct answer, as he lead me to the solution. But I also want to post the entire answer here, just for posterity.

            List<DistributorCustomer> customerList = xDoc.Descendants("Customer")
            .Select(c => new DistributorCustomer
            {
                CustomerName = c.Element("CustomerName")?.Value,
                AccountNumber = c.Element("AccountNumber")?.Value,
                BillingAddress = new BillingAddress
                {
                    Address1 = c.Element("BillingAddress")?.Element("Address1")?.Value,
                    Address2 = c.Element("BillingAddress")?.Element("Address2")?.Value,
                    City = c.Element("BillingAddress")?.Element("City")?.Value,
                    State = c.Element("BillingAddress")?.Element("State")?.Value,
                    Zip = c.Element("BillingAddress")?.Element("Zip")?.Value,
                    Country = c.Element("BillingAddress")?.Element("Country")?.Value,
                },
                ShippingAddress = new ShippingAddress
                {
                    Address1 = c.Element("ShippingAddress")?.Element("Address1")?.Value,
                    Address2 = c.Element("ShippingAddress")?.Element("Address2")?.Value,
                    City = c.Element("ShippingAddress")?.Element("City")?.Value,
                    State = c.Element("ShippingAddress")?.Element("State")?.Value,
                    Zip = c.Element("ShippingAddress")?.Element("Zip")?.Value
                }
            }).ToList();
Casey Crookston
  • 13,016
  • 24
  • 107
  • 193