Deserializing array from XML data (in ServiceStack

2019-04-30 14:48发布

I've got the following chunk of XML data:

<ArrayOfRESTDataSource xmlns="http://SwitchKing.Common/Entities/RESTSimplified/2010/07" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<RESTDataSource>
    <Description>MyTest</Description>
    <Enabled>true</Enabled>
 </RESTDataSource>
</ArrayOfRESTDataSource>

RESTDataSource can occur 0-n times.

And here's my classes:

[DataContract( Namespace = "http://SwitchKing.Common/Entities/RESTSimplified/2010/07" )]
public class ArrayOfRESTDataSource
{
    public RESTDataSource[] Data { set; get; }  
}


[DataContract( Namespace = "http://SwitchKing.Common/Entities/RESTSimplified/2010/07" )]
public class RESTDataSource
{
    [DataMember]
    public bool Enabled { get; set; }
    [DataMember]
    public string Description { get; set; }
}

I read the above XML data from a server like this:

WebRequest client = WebRequest.Create( "http://server:80/datasources" );
using( StreamReader sr = new StreamReader( client.GetResponse().GetResponseStream()) ) {
    string xml = sr.ReadToEnd();
var response = ServiceStack.Text.XmlSerializer.DeserializeFromString<ArrayOfRESTDataSource>( xml );
}

My question is: What do I need to change or decorate public RESTDataSource[] Data with to get the deseralization to work for the array? Serializing single RESTDataSource items work just fine, its just the array I can't get to work.

Thanks in advance.

Update 1

As @mythz suggested, I updated my code to this, but response.Data is still null. What did I not understand?

[DataContract( Namespace = "http://SwitchKing.Common/Entities/RESTSimplified/2010/07" )]
 public class ArrayOfRESTDataSource
{
    [DataMember]
    public DataSource Data { set; get; }
}

[CollectionDataContract( ItemName = "RESTDataSource" )]
public class DataSource : List<RESTDataSource>
{
    public DataSource() { }
    public DataSource( IEnumerable<RESTDataSource> collection ) : base( collection ) { }
}

Update 2

The solution is in @mythz answer below, but just for completeness/clarity: What I did wrong was to add another level in my DTOs - the top-level class ArrayOfRESTDataSource is the one that actually has the sub items in XML so it is that one that should be of a collection type.

1条回答
▲ chillily
2楼-- · 2019-04-30 15:38

You can use [CollectionDataContract(...)] to modify the serialization output for Arrays/Collections, see this previous answer for an example.

Debugging Serialization and Integration issues

The first step when trying to solve integration problems like this is to isolate the problem. i.e. remove everything else away and just focus on the problem. e.g. In this case I would just focus on the XML and the DTOs and decouple them from your services.

ServiceStack just uses .NET's Xml DataContractSerializer under the hood and doesn't add any extra transformations or byte overheads (it's just the raw DTOs serialized as-is), so if you can get it working outside of your services you can put the same DTOs back into your service and it will also work over the wire.

Handy ServiceStack Extension methods

ServiceStack provides convenient extension methods to serialize / de-serialize and analyze your data models:

Serialization / De-Serialization Extension methods

T.ToJson() / string.FromJson<T>()  //Serialize JSON
T.ToJsv() / string.FromJsv<T>()    //Serialize JSV  
T.ToXml() / string.FromXml<T>()    //Serialize XML

Handy Dump Utils

Recursively print a object-graph in Pretty JSV Dump Format

T.PrintDump()      

Print a string to the console resolving string.Format() args (if any)

string.Print(args)    

What do your Serialized DTOs look like

The first step you should be doing when trying to come up with the Shape of the DTOs is to populate and print them to see what it looks like. Looking at the chunk of XML output I came up with these DTOs:

[DataContract(Namespace = "http://SwitchKing.Common/Entities/RESTSimplified/2010/07")]
public class RESTDataSource
{
    [DataMember]
    public bool Enabled { get; set; }
    [DataMember]
    public string Description { get; set; }
}

[CollectionDataContract(ItemName = "RESTDataSource", Namespace = "http://SwitchKing.Common/Entities/RESTSimplified/2010/07")]
public class ArrayOfRESTDataSource : List<RESTDataSource>
{
    public ArrayOfRESTDataSource() { }
    public ArrayOfRESTDataSource(IEnumerable<RESTDataSource> collection) : base(collection) { }
}

Then populate and dump them:

var dto = new ArrayOfRESTDataSource { 
    new RESTDataSource { Enabled = true, Description = "MyTest" } };

dto.ToXml().Print();

Which prints to the Console:

<?xml version="1.0" encoding="utf-8"?><ArrayOfRESTDataSource xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://SwitchKing.Common/Entities/RESTSimplified/2010/07"><RESTDataSource><Description>MyTest</Description><Enabled>true</Enabled></RESTDataSource></ArrayOfRESTDataSource>

Which looks like what we want. If it doesn't tweak the above DTOs until you get the same chunk as your expected XML.

When you have the DTOs in the same shape of the XML you can start trying to serialize them:

var dto = xml.FromXml<ArrayOfRESTDataSource>();
dto.PrintDump();

Which will print this pretty object graph:

[
    {
        Enabled: True,
        Description: MyTest
    }
]

If all the fields are populated with the expected values then you are done and can update your ServiceStack web service DTOs.

查看更多
登录 后发表回答