XML Deserialization with Servicestack.Text
Asked Answered
J

2

9

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.

Jourdain answered 18/2, 2013 at 16:46 Comment(0)
M
24

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.

Mercymerdith answered 21/2, 2013 at 18:19 Comment(5)
You mean I have no way to do this with servicestack?Jourdain
You cannot use ServiceStack.Text for this. But realize that servicestack's focus is web services; in particular services you provide. The .net framework has a fine xml serializer already; it just uses that. Using servicestack only for its XML serialization just doesn't add much - you could implement FromXml<> in just a few lines of code - it's not much more than new DataContractSerializer(typeof(T)).ReadObject(reader). So it's nice to have if you're using servicestack anyhow, but it's hardly a critical feature.Mercymerdith
ServiceStack.Text can do this with JSON, however -- and quite easily.Napalm
@DanEsparza ...sure, but, well, RSS isn't JSON?Mercymerdith
@EamonNerbonne sheepish grin ... that'll teach me to pay attentionNapalm
G
0

A few things:

  1. Since you are using the [DataContract] attribute. You must include the DTOs properties with the [DataMember] Attribute or they will be skipped in the serialization/deserialization process. Use the assembly attribute as specified in XML deserializing only works with namespace in xml

  2. Your xml manipulation needs to change to wrap the rss inside a response instead or replacing it.

    xml = xml.Replace("<rss version=\"2.0\">", "<Response><rss version=\"2.0\">");

  3. I would recommend building an test Response object yourself, serlialize it to XML using ServiceStack's .ToXml() method to see the format it is expecting. You will see service stack handles the channel items as a child list of items that is not how the RSS formats the channel items. You would have to wrap all your items into a node called <ItemClass>

Gildus answered 18/2, 2013 at 18:50 Comment(5)
1. Tried it and still same result 2. I failed to understand it 3.The structure you have suggested is the same as I have put in the question , isn't it?Jourdain
Make sure you have the correct casing on your properties. You will need to change "Channel" to "channel". Also see this gist gist.github.com/jokecamp/4979703 for how ServiceStack will serialize your DTOs when you use the assembly: ContractNamespace attribute.Gildus
If I am wrong with some casing then , they should be null only, right? I am getting whole rss object null.Jourdain
I updated the gist to include some test code gist.github.com/jokecamp/4979703. Compare it with yours and run mine.Gildus
Thanks but it is not working. It is not deserializing it properly, it gives null values of version and channel onlyJourdain

© 2022 - 2024 — McMap. All rights reserved.