1

I have figured out how to populate a custom class from XML data, but I ran into an issue along the way. Things were working perfectly with my existing method of populating data until I was thrown a bit of a curve ball. The new schema I was sent is similar to this:

<ITEM_REPLY>
<TRAN_ID>1320691307345</TRAN_ID>
<REPLY_CODE>0</REPLY_CODE>
<UNIT_PRICE>8.2784</UNIT_PRICE>
<SUP_LOCS>
  <SUP_LOC>
     <SUP_LOC_ID>001134</SUP_LOC_ID>
     <COUNTRY_ID>USA</COUNTRY_ID>
     <QTY_AVL>47.000</QTY_AVL>
     <ITEM_UOM>EA</ITEM_UOM>
  </SUP_LOC>
  <SUP_LOC>
     <SUP_LOC_ID>006817</SUP_LOC_ID>
     <COUNTRY_ID>USA</COUNTRY_ID>
     <QTY_AVL>20.000</QTY_AVL>
     <ITEM_UOM>EA</ITEM_UOM>
  </SUP_LOC>
</SUP_LOCS>
<MESSAGE />
<QTY_BREAKS />
</ITEM_REPLY>

Pretty standard XML schema, problem is I'm not sure how to populate my custom class with it. Here's what I do have:

static void Main(string[] args)
{
    var order = ConvertXMLMessage<ItemReply>(request);
}

protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
    var xml = new XmlDocument();
    xml.LoadXml(xmlData);

    var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

    using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
    {
        T work = (T)(serializer.Deserialize(xmlReader));
        return work;
    }
}

public class ItemReply
{
    [XmlElement("ITEM_REPLY")]
    public ItemAvlReply ITEM_REPLY { get; set; }
}
public class ItemAvlReply
{
    [XmlElement("TRAN_ID")]
    public string TRAN_ID { get; set; }
    [XmlElement("REPLY_CODE")]
    public string REPLY_CODE { get; set; }
    [XmlElement("UNIT_PRICE")]
    public string UNIT_PRICE { get; set; }
    [XmlElement("SUP_LOCS")]
    public SupplierLocations SUP_LOCS;
    [XmlElement("MESSAGE")]
    public string MESSAGE { get; set; }
    [XmlElement("QTY_BREAKS")]
    public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
    [XmlElement("SUP_LOC")]
    public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
    [XmlElement("SUP_LOC_ID")]
    public string SUP_LOC_ID { get; set; }
    [XmlElement("COUNTRY_ID")]
    public string COUNTRY_ID { get; set; }
    [XmlElement("QTY_AVL")]
    public string QTY_AVL { get; set; }
    [XmlElement("ITEM_UOM")]
    public string ITEM_UOM { get; set; }
}

This works perfectly minus the List<Item> part. I'm not overly experienced with LINQ and I'm not sure how to go about declaring a sub array in my class via this statement. I am also open to a different approach from creating the List<Item> part, I'm just not sure where to start otherwise. Is there a better approach for what I'm need to do? Is there an easy solution I am just unaware of in LINQ?

Volearix
  • 1,573
  • 3
  • 23
  • 49

1 Answers1

2

Here's a simple way to do it, assuming the example XML file you provided has typos. I assumed the OrderId has a closing tag, and that the closing tag for Items should be /Items.

Here's the version of the xml I used:

<Order>
<TransactionID>123</TransactionID>
<OrderID>1</OrderID>
<Items Number="2">
    <Item>
        <ItemName>Test</ItemName>
        <Color>Red</Color>
    </Item>
    <Item>
        <ItemName>Test1</ItemName>
        <Color>Blue</Color>
    </Item>
</Items>
</Order>

Here's the code to read/write the XML: (the xml variable is a String)

var order = ConvertXMLMessage<Order>(xml);
WriteXMLFile<Order>(order, @"test.xml");

Here's the ConvertXMLMessage and WriteXMLFile functions:

protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
    var xml = new XmlDocument();
    xml.LoadXml(xmlData);

    var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

    using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
    {
        T work = (T)(serializer.Deserialize(xmlReader));
        return work;
    }
}

protected static void WriteXMLFile<T>(T item, String saveLocation) where T : class, new()
{
    System.Xml.Serialization.XmlSerializer writer = new System.Xml.Serialization.XmlSerializer(typeof(T));

    System.IO.StreamWriter file = new System.IO.StreamWriter(saveLocation);
    writer.Serialize(file, item);
    file.Close();
}

and here's the class structure:

public class Order
{
    [XmlElement("TransactionID")]
    public string TransactionId { get; set; }
    [XmlElement("OrderID")]
    public string OrderId { get; set; }
    [XmlElement("Items")]
    public ItemsContainer Items;
}

public class ItemsContainer
{
    [XmlAttribute("Number")]
    public Int32 Number { get; set; }

    [XmlElement("Item")]
    public List<Item> Items;
}

public class Item
{
    [XmlElement("ItemName")]
    public string ItemName { get; set; }
    [XmlElement("Color")]
    public string Color { get; set; }
}

As you'll notice I added some attributes to let the XML parser know how to handle the class when it's converting from/to the XML. I also added another small class called "ItemsContainer" just to hold the details on the Items tag. If you didn't need the "Number" attribute, then you could probably find a way to do away with this. However, this should get you in the right direction.

The example I provided is a simple version of how I usually handle the situation, obviously there's some improvements you can make depending on your needs.

Edit I changed the Item class to use ItemName instead of TransactionId. It was an oversight on my part.

Edit 2 Here's the corrections you need to make to the newly posted code. The reason the Order class worked in the previous example was it matched the root XML element. You're new XML does align with the base class. So we need to add in a couple more attributes to make this work. You can also remove your ItemReply class. It's not needed.

So here's the new classes:

[XmlRoot("ITEM_REPLY")]
public class ItemAvlReply
{
    [XmlElement("TRAN_ID")]
    public string TRAN_ID { get; set; }
    [XmlElement("REPLY_CODE")]
    public string REPLY_CODE { get; set; }
    [XmlElement("UNIT_PRICE")]
    public string UNIT_PRICE { get; set; }
    [XmlElement("SUP_LOCS")]
    public SupplierLocations SUP_LOCS;
    [XmlElement("MESSAGE")]
    public string MESSAGE { get; set; }
    [XmlElement("QTY_BREAKS")]
    public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
    [XmlElement("SUP_LOC")]
    public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
    [XmlElement("SUP_LOC_ID")]
    public string SUP_LOC_ID { get; set; }
    [XmlElement("COUNTRY_ID")]
    public string COUNTRY_ID { get; set; }
    [XmlElement("QTY_AVL")]
    public string QTY_AVL { get; set; }
    [XmlElement("ITEM_UOM")]
    public string ITEM_UOM { get; set; }
}

Everything else should remain the same. The parsing/converting the XML to classes should work without any changes.

Nathan
  • 1,437
  • 12
  • 15
  • I am getting and error `There is an error in the XML document.` – Volearix Nov 06 '14 at 18:28
  • Can you give me a bit more to go on? Are you using the XML I provided (I changed some pieces from the original because it looked like there was typos)? What .Net framework are you using? I've got the code above running in a test console job without any errors. I'm using .Net 4.5. If you're using the same framework and test XML then I'd need to see the code you implemented. – Nathan Nov 06 '14 at 18:52
  • I'm going to fairly drastically change the above question. I posted a "dumbed" down xml and class definition just to get my point across. Give me a second and you can let me know if anything looks off. – Volearix Nov 06 '14 at 18:55
  • It's updated. Here is the exact error as well `{System.InvalidOperationException: was not expected.at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderItemReply.Read6_ItemReply()}` – Volearix Nov 06 '14 at 19:02
  • I added an edit to the answer to fully explain the problem, but it boils down to the root XML tag doesn't match your class name. – Nathan Nov 06 '14 at 19:11
  • Beautiful! If I could give you more thumbs up I would, Been racking my brain about this one for 2 days and looks like it's working now. – Volearix Nov 06 '14 at 19:22