3

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

1

6 Answers 6

3

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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 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.
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.
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();

5 Comments

Why would you mix in the XmlSerializer with a LINQ statement when LINQ can do it all by itself?
Because, with LINQ, you have to create custom code for each different type of object. This would work for different objects as well.
@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.
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.
With the generic, parameterized version, I can see the value in what your doing. +1 You should remove the non-generic version.
1

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.

Comments

1

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);
}

Comments

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});

Comments

-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>;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.