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:
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.
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.
A few things:
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
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\">");
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>
Related
I'm working with the .NET Serialization support. I need to use the Google Geocoding API to retrieve the results of a geocoding query as XML, and deserialize the XML to a C# class. The problem is, the C# class has a property that matches to an XML element that may or may not be present in the XML stream.
I've looked through the MSDN documentation for XML serialization/deserialization for a way to handle this, but nothing jumps out. Is there a way to specify that an element is optional in the XML stream?
Here is the C# class to contain the deserialized XML:
[XmlRoot]
public class MyGeocodeResponse
{
[XmlElement("status")]
public string Status { get; set; }
[XmlElement("result")]
public Result[] Results { get; set; }
[XmlElement("partial_match")]
public bool PartialMatch { get; set; }
}
The "partial_match" element appears to be optional. When I deserialize some XML that does not have the "partial_match" element, an exception is thrown (InvalidOperationException).
Is there a way to specify that the "partial_match" element may not be present?
Did you try to use DataContract and [DataMember(IsRequired = false) instead?
[DataContract(Namespace ="youNamespace")]
public class MyGeocodeResponse
{
[DataMember(Name="status")]
public string Status { get; set; }
[DataMember(Name="result")]
public Result[] Results { get; set; }
[DataMember(Name="partial_match", IsRequired = false)]
public bool PartialMatch { get; set; }
}
If the element may be present but it may have a Null value, then use this:
[XmlElement("partial_match", IsNullable = true)]
If the element may not be present at all, then do this:
private bool? partialMatch;
[XmlElement("partial_match")]
public bool PartialMatch
{
get { return this.partialMatch; }
set { this.partialMatch = value; this.PartialMatchSpecified = true; }
}
[XmlIgnore]
public bool PartialMatchSpecified { get; set; }
How can we parse if json fields contains a colon(:)? Like this:
{
"dc:creator":"Jordan, Micheal",
"element:publicationName":"Applied Ergonomics",
"element:issn":"2839749823"
}
In fact I wonder how to do this with a library like restsharp, for mapping?
Using Json.Net
string json = #"{
""dc:creator"":""Jordan, Micheal"",
""element:publicationName"":""Applied Ergonomics"",
""element:issn"":""2839749823""
}";
var pub = JsonConvert.DeserializeObject<Publication>(json);
public class Publication
{
[JsonProperty("dc:creator")]
public string creator { set; get; }
[JsonProperty("element:publicationName")]
public string publicationName { set; get; }
[JsonProperty("element:issn")]
public string issn { set; get; }
}
OR
Console.WriteLine(JObject.Parse(json)["dc:creator"]);
If you use DataContractJsonSerializer, DataMemberAttribute has property Name which can be used to override default name. This means that when you deserialize json value of property dc:creator is assigned to Publication::Creator property and on the contrary when you serialize C# object.
For example:
public class Publication
{
[DataMember(Name="dc:creator")]
public string Creator { set; get; }
[DataMember(Name="element:publicationName")]
public string PublicationName { set; get; }
[DataMember(Name="element:issn")]
public string Issn { set; get; }
}
If you choose to use Json.Net, #L.B's answer is the way to go.
I'm developing my own JSON-to-POCO framework.
The way I'm doing it is, that I created the class of a Json-schema that looks like this:
public class JsonSchema
{
public string type { get; set; }
public string title { get; set; }
public string description { get; set; }
public Dictionary<string, JsonSchema> properties { get; set; }
public JsonSchema items { get; set; }
public string id { get; set; }
public List<string> required { get; set; }
public bool ispartial = true;
}
and deserializing the schema to have my object(s) so I can work with it perfectly.
Everything is working fine for now, I'm getting my generated C#-file. But only if I don't add $refs to my json. (Since it's much less code to add a reference in json instead of copy-paste the classes I want to support them)
What I need to do is, adding the $ref to my class. Problem here is, I cannot add an attribute like
public string $ref { get; set; }
to my code.
Any idea what I could do?
The problem was also, that you cannot deserialize $id and $ref with default settings. This was solved by reading this nice post here: JsonConvert.DeserializeObject<> (string) returns null value for $id property
You may add [JsonProperty] attribute to the property you want to change the name.
[JsonProperty("$ref")]
public string reference { get; set; }
I have xml that is not very well formed, but need to map to a List with RestSharp. I do not have control of the service/ xml output. Thus far, I was able to get around issues with the properties themselves using the DeserializeAs(Name="name")] property. For instance,
public class Physician
{
[DeserializeAs(Name = "personId")]
public string Id { get; set; }
[DeserializeAs(Name = "fName")]
public string FirstName { get; set; }
[DeserializeAs(Name = "lName")]
public string LastName { get; set; }
}
Maps correctly to a list when I have the following xml:
<data>
<physician>
<personId>3325</personId>
<fName>Foo</fName>
<lName>Bar</lName>
</physician>
<physician>
<personId>3342</personId>
<fName>Jane</fName>
<lName>Doe</lName>
</physician>
...
</data>
The function I am using is:
public static List<T> GetListOfEntityType<T>(string url)
{
return Client.Execute<List<T>>(new RestRequest(url)).Data;
}
The problem is that I have xml that looks like this for a number of other requests,
<data>
<row>
<typeId>0</typeId>
<type>Physician</type>
</row>
<row>
<typeId>1</typeId>
<type>Ambulance</type>
</row>
...
</data>
Given it is not very descriptive xml, but I need to map this to a List.
public class OrganizationType
{
public string typeId { get; set; }
public string type { get; set; }
}
https://stackoverflow.com/a/4082046/3443716 somewhat answers this, and it certainly works, but I do not want the model to be named row I tried to do this:
[DeserializeAs(Name = "row")]
public class OrganizationType
{
public string typeId { get; set; }
public string type { get; set; }
}
However RestSharp appers to ignore this attribute entirely. I have been searching a ton and found a few answers that suggest using a custom deserializer, but I have a hard time believing that is the only or easiest option for that matter. Is there some other attribute that I may be missing or is the only option using a custom deserializer?
As another note, I also tried to do something like this and I just get null back....
public class OrganizationType
{
public string typeId { get; set; }
public string type { get; set; }
}
public class OrgTypeCollection
{
[DeserializeAs(Name = "row")]
public List<OrganizationType> Names { get; set; }
}
Thanks to this post, https://stackoverflow.com/a/27643726 I was able to "fork" the RestSharp Deserialzier and create a slightly custom one with the two line modification provided by The Muffin Man as follows
Added this to HandleListDerivative in the RestSharp.Deserializers.XmlDeserializer at line 344.
var attribute = t.GetAttribute<DeserializeAsAttribute>();
if (attribute != null) name = attribute.Name;
That allowed me to as desired add DeserializeAs as follows:
[DeserializeAs(Name = "row")]
public class OrganizationType
{
public string typeId { get; set; }
public string type { get; set; }
}
I am unsure why this is ignored by restsharp, this seems like it would be useful in a number of cases... As a side note, the functionality of creating nested lists is still available as well. Though I haven't run the tests after modification, it appears to do exactly what you would expect. Other than that all you have to do is add the custom handler to rest by callling
Client.AddHandler("application/xml", new CustomXmlDeserializer());
I am trying to deserialise a json object. The Problem is that the the object also contains subarrays
http://i.imgur.com/WWwEVLR.png
Except for the subarrays everything is working.
I am using Newtonsoft.Json;
Here is my class
public string date_updated { get; set; }
public string date_expires { get; set; }
This is working fine.
For the subarray I did it that way:
public JsonArray contacts { get; set; }
This is my method to deserialise it:
var json = await webClient.GetAsync(new Uri(uri));
var result = await json.Content.ReadAsStringAsync();
Model = JsonConvert.DeserializeObject<Model>(result);
The Array is created well with all fields needed, but the values are not working.
The values are just: Windows.Json.JsonObject as on the picture below.
http://i.imgur.com/Q8bpCoD.png
Why is he not writing the values? How can I get them?
Thank you for your help.
The values are working fine. Using JsonArray tells the deserializer to convert the JSON data to something that is compatible with the type JsonArray. This type is simply a 1:1 representation of the JSON string underneath and is not deserialized into useful data automatically for you.
Also, JsonArray is not even part of the Json.Net library. As the debugger is telling you, it is part of the Windows.Data.Json namespace which is in a different library. You could still access the data directly from each JsonObjects using the various Get methods inside the class ( http://msdn.microsoft.com/en-us/library/windows.data.json.jsonobject.aspx ) but this is probably not the way you want to go.
In your case, you should create a class that represents the data you have inside each of those arrays. If not all entries in the array contains all of the properties of your class, don't worry. Json.Net will simply leave their value empty when deserializing. This will look like:
public class Contact
{
public string type { get; set; }
public string name { get; set; }
public string organization { get; set; }
public string full_address { get; set; }
etc.
}
For good measure, you should also respect the C# naming convention which states that properties should use CamelCase names. To help you with this, you can use the JsonProperty attribute like so:
public class Contact
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("organization")]
public string Organization { get; set; }
[JsonProperty("full_address")]
public string FullAddress { get; set; }
etc.
}
Then, you can replace the type of your contacts property to List<Contact> and the data will be automatically deserialized to a format that you can easily use.
Define new class
class Contact {
public string type { get; set; }
public string name { get; set; }
// etc
}
and modify your ReqInfo_WhoIs_Model class
public string date_updated { get; set; }
public string date_expires { get; set; }
public List<Contact> contacts { get; set; }