2

I have an XML file:

<?xml version="1.0" encoding="UTF-8"?>
<MyProducts>
    <Product Name="P1" />
    <Product Name="P2" />
</MyProducts>

And a C# object:

Public Class Product
{
    [XmlAttribute]
    Public Name {get; set;}
}

Using the .NET Serializer class now can I Deserialize the XML file into an IList without creating a MyProducts object?

So I want to somehow select only the Product elements before I serialize

George Johnston
  • 31,652
  • 27
  • 127
  • 172
Bob
  • 21
  • 1

6 Answers6

2

If you don't want to create a collection class for your products, you can mix some LINQ to XML with the XmlSerializer:

public static IEnumerable<T> DeserializeMany<T>(
    string fileName, string descendentNodeName = null) {
  descendentNodeName = descendentNodeName ?? typeof(T).Name;
  var serializer = new XmlSerializer(typeof(T));
  return
    from item in XDocument.Load(fileName).Descendants(descendentNodeName)
    select (T)serializer.Deserialize(item.CreateReader());
}

Then, to get your list:

var products = XmlSerializerUtils.DeserializeMany<Product>(fileName).ToList();
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • Why would you mix in the XmlSerializer with a LINQ statement when LINQ can do it all by itself? – Dave White Nov 09 '10 at 16:22
  • Because, with LINQ, you have to create custom code for each different type of object. This would work for different objects as well. – Jordão Nov 09 '10 at 16:26
  • @Jordao - You're already creating custom code looking for "Product" nodes in the root and casting the output of the serializer to `Product` so I don't see the value in using the serializer. The only thing you've achieved is having the serializer new up the `Product` object for you and that isn't obvious looking at the code. – Dave White Nov 09 '10 at 16:30
  • I've added a more general method there.... It's the same code, only parameterized. With only LINQ, you'd need different code for different classes. Also, if your class changes, you'd need to update the code. You see, this solution is more maintainable, and if you're already using the xml serializer, it's a better path to take. – Jordão Nov 09 '10 at 16:31
  • With the generic, parameterized version, I can see the value in what your doing. +1 You should remove the non-generic version. – Dave White Nov 09 '10 at 16:38
2

There's a quick-and-dirty way to accomplish what you want - simply replace "MyProducts" with something the BCL classes are happy with - ArrayOfProduct:

string xml = @"<?xml version='1.0' encoding='UTF-8;'?> 
  <MyProducts> 
      <Product Name='P1' /> 
      <Product Name='P2' /> 
  </MyProducts>";
//secret sauce:
xml = xml.Replace("MyProducts>", "ArrayOfProduct>");
IList myProducts;
using (StringReader sr = new StringReader(xml))
{
    XmlSerializer xs = new XmlSerializer(typeof(List<Product>));
    myProducts = xs.Deserialize(sr) as IList;
}
foreach (Product myProduct in myProducts)
{
    Console.Write(myProduct.Name);
}

Of course, the right way would be to transform the XML document to replace the MyProducts nodes appropriately - instead of using a string replace - but this illustrates the point.

Jordão
  • 55,340
  • 13
  • 112
  • 144
Rex M
  • 142,167
  • 33
  • 283
  • 313
  • 1
    I didn't DV your answer, but I don't like the idea of manipulating the source XML document the way that you are, or recommending that someone manipulate the source XML document in order to make their code work. – Dave White Nov 09 '10 at 16:31
  • @Dave I think you misunderstand the answer. Intermediate transformations to get data from one format to another is pretty common, and as I stated at the end the string replace is to illustrate the point in a simple way. – Rex M Nov 09 '10 at 16:36
  • I didn't misunderstand your answer. I just didn't see the manipulation of the source XML document as necessary. It does work, and demonstrates an understanding of what the XmlSerializer is looking for, it just would not be my preferred solution. – Dave White Nov 09 '10 at 16:46
0

I don't think you can instruct the serializer to spit out a IList. The serializer can create a MyProduct collection object and fill it with Products. Which sounds like what you want to do.

You can also use LINQ to query the XML document and create a list of IEnumerable as well.

// load from stream if that is the case 
// this just uses a file for demonstration purposes
XDocument doc = XDocument.Load("location_of_source.xml");

// select all Product nodes from the root node and create a new Product object using
// object initialization syntax
var listOfProduct = doc.Descendants("Product")
                       .Select(p => new Product { Name = p.Attribute("Name").Value});
Dave White
  • 3,451
  • 1
  • 20
  • 25
0

I'm not sure you're going to have much success using the XML serializer to accomplish what you need. It may be simpler for you to manually parse out the XML and map them explicitly, e.g.

        XDocument xml = XDocument.Parse(@"<?xml version=""1.0"" encoding=""UTF-8""?>
                                            <MyProducts>
                                                <Product Name=""P1"" /> 
                                                <Product Name=""P2"" /> 
                                            </MyProducts>");

        foreach(var product in xml.Descendants(XName.Get("Product")))
        {
            var p = new Product { Name = product.Attribute(XName.Get("Name")).Value };
           // Manipulate your result and add to your collection.
        }

        ...

        public class Product
        {
           public string Name { get; set; }
        }

If you're using a file, which you most likely are for your XML, just replace the Parse method on the XDocument w/ Load and the appropriate signature.

George Johnston
  • 31,652
  • 27
  • 127
  • 172
0

While it's novel to not create the class, doing so saves you a lot of code... You don't even have to use it except when you deserialize.

//Products declaration
[XmlRoot(ElementName = "MyProducts")]
public class MyProducts : List<Product>
{
}

public class Product
{
    [XmlAttribute]
    public string Name { get; set; }
}

...

[Test]
public void DeserializeFromString()
{
    var xml = @"<?xml version='1.0' encoding='UTF-8;'?>  
      <MyProducts>  
        <Product Name='P1' />  
        <Product Name='P2' />  
      </MyProducts>";

    IList<Product> obj = Serialization<MyProducts>.DeserializeFromString(xml);

    Assert.IsNotNull(obj);
    Assert.AreEqual(2, obj.Count);
}

...

//Deserialization library
public static T DeserializeFromString(string serialized)
{
    var byteArray = Encoding.ASCII.GetBytes(serialized);
    var memStream = new MemoryStream(byteArray);
    return Deserialize(memStream);
}

public static T Deserialize(Stream stream)
{
    var xs = new XmlSerializer(typeof(T));
    return (T) xs.Deserialize(stream);
}
Jordão
  • 55,340
  • 13
  • 112
  • 144
Austin Salonen
  • 49,173
  • 15
  • 109
  • 139
-1

The problem is that IList is not serializable so you'd have to implement your custom XmlSerializer - walk through xml nodes etc, you can however deserialize your collection into List using out of the box XmlSerializer.

Dont forget to add default constructor to your Product Class.

        XmlSerializer serializer = new XmlSerializer(typeof(List<Product>));
        FileStream stream = new FileStream(fileName, FileMode.Open); 
        var product = serializer.Deserialize(sream) as List<Product>;
dexter
  • 7,063
  • 9
  • 54
  • 71