Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML Deserialization with Servicestack.Text

I am learning Servicestack.Text Library as it has some of the best features.I am trying to deserialize XML into one of my DTOs as below;

C# Code:[Relevant Code with Console Application Here]

class Program
    {
        static void Main(string[] args)
        {
            string str = "http://static.cricinfo.com/rss/livescores.xml";
            WebClient w = new WebClient();
            string xml = w.DownloadString(str);
            Response rss = xml.FromXml<Response>();
            foreach (var item in rss.rss.Channel.item)
            {
                Console.WriteLine(item.title);
            }
            Console.Read();
        }
    }

You can go through the XML file at str[Given in the program]. I have prepared DTOs for the deserialization. They are as below:

public  class Response
{
   public RSS rss { get; set; }
}

public class RSS
{
   public string Version { get; set; }
   public ChannelClass Channel { get; set; }
}

public class ChannelClass
{
   public string title { get; set; }
   public string ttl { get; set; }
   public string description { get; set; }
   public string link { get; set; }
   public string copyright { get; set; }
   public string language { get; set; }
   public string pubDate { get; set; }
   public List<ItemClass> item { get; set; }
}

public class ItemClass
{
   public string title { get; set; }
   public string link { get; set; }
   public string description { get; set; }
   public string guid { get; set; }
}

When I run the program, I get an exception as shown below:

enter image description here

So, to change the Element and the namespace, I did following workaround:

I put the DataContractAttribute on my Response class as below:

[DataContract(Namespace = "")]
public  class Response
{
   public RSS rss { get; set; }
}

I changed the Element name as below by adding following two lines just before deserializing

  //To change rss Element to Response as in Exception
  xml = xml.Replace("<rss version=\"2.0\">","<Response>");
  //For closing tag
  xml = xml.Replace("</rss>","</Response>");

But, it gave another exception on the foreach loop as the deserialized rss object was null. So, how should I deserialize it in a proper way using Servicestack.Text?

Note :

I know well how to deserialize with other libraries, I want to do it with ServiceStack only.

like image 675
Bhushan Firake Avatar asked Feb 18 '13 16:02

Bhushan Firake


1 Answers

TLDR: Use XmlSerializer to deserialize from xml dialects you can't control; ServiceStack is designed for code-first development and can not be adapted to general purpose xml parsing.


ServiceStack.Text does not implement a custom Xml serializer - it uses DataContractSerializer under the hood. FromXml is merely syntactic sugar.

Using DataContractSerializer to parse Xml

As you've noticed, DataContractSerializer is picky about namespaces. One approach is to specify the namespace explicitly on the class, but if you do this, you'll need to specify [DataMember] everywhere since it assumes that if anything is explicit, everything is. You can work around this problem using an assembly-level attribute (e.g. in AssemblyInfo.cs) to declare a default namespace:

[assembly: ContractNamespace("", ClrNamespace = "My.Namespace.Here")]

This solves the namespace issue.

However, you cannot solve 2 other issues with DataContractSerializer:

  • It will not use attributes (in your case, version)
  • It requires that collections such as item have both a wrapping name and an element name (something like items and item)

You cannot work around these limitations because DataContractSerializer is not a general-purpose XML parser. It is intended to easily produce and consume an API, not map arbitrary XML onto a .NET datastructure. You will never get it to parse rss; so therefore ServiceStack.Text (which just wraps it) can also not parse it.

Instead, use XmlSerializer.

Using XmlSerializer

This is rather straighforward. You can parse input with something along the lines of:

var serializer = new XmlSerializer(typeof(RSS));
RSS rss = (RSS)serializer.Deserialize(myXmlReaderHere);

The trick is to annotate the various fields such that they match your xml dialect. For example, in your case that would be:

[XmlRoot("rss")]
public class RSS
{
    [XmlAttribute]
    public string version { get; set; }
    public ChannelClass channel { get; set; }
}

public class ChannelClass
{
    public string title { get; set; }
    public string ttl { get; set; }
    public string description { get; set; }
    public string link { get; set; }
    public string copyright { get; set; }
    public string language { get; set; }
    public string pubDate { get; set; }
    [XmlElement]
    public List<ItemClass> item { get; set; }
}

public class ItemClass
{
    public string title { get; set; }
    public string link { get; set; }
    public string description { get; set; }
    public string guid { get; set; }
}

So some judicious attributes suffice to get it to parse the XML as you want.

In summary: you cannot use ServiceStack for this since it uses DataContractSerializer.ServiceStack/DataContractSerializer are designed for scenarios where you control the schema. Use XmlSerializer instead.

like image 100
Eamon Nerbonne Avatar answered Nov 15 '22 17:11

Eamon Nerbonne