Xml Deserialization - Sequence of Mutiple Types - c#

Given the following fragment where links is a sequence of unbounded imagelinks and documentlinks, what should the deserailized class be?
<Values>
<Links>
<ImageLink>http://#</ImageLink>
<ImageLink>http://#</ImageLink>
<DocumentLink>http://#</DocumentLink>
</Links>
</Values>
Typically, if it was just an array of imagelinks I might have
public class Values
{
public imagelink[] ImageLinks { get; set; }
}
public class ImageLink
{
public string Value { get; set; }
}
But with the above xml I'm stumped.
Btw, I have no control over the xml.

This worked
public class DocumentLink : Link
{
}
public class ImageLink : Link
{
}
public class Link
{
[XmlText]
public string Href { get; set; }
}
public class Values
{
[XmlArrayItem(ElementName = "ImageLink", Type = typeof(ImageLink))]
[XmlArrayItem(ElementName = "DocumentLink", Type = typeof(DocumentLink))]
public Link[] Links { get; set; }
}

You should have a base class Link as follows
public class Link
{
public string Href { get; set; }
}
public class ImageLink : Link
{
}
public class DocumentLink : Link
{
}
And your values class would look like:
public class Values
{
public Link[] links { get; set; }
}
Alternatively, you could use ArrayList instead of strong typed array.

Related

How can I ignore the property name that I use in the jsonproperty attribute, provided that it is only once

My problem is that I deserialize the incoming data and transfer it to this class using jsonproperty. but I don't want to use property name attribute that I use in jsonproperty while serializing.
example class
public class DocumentDetail
{
[JsonProperty("KAREKOD")]
public string qrCode { get; set; }
[JsonProperty("GTIN")]
public string gtinNumber { get; set; }
[JsonProperty("LOTNUMBER")]
public string lotNumber { get; set; }
}
example serialize
{
DocumentDetail docDetail=new DocumentDetail(){qrCode="123456adsfg789",gtinNumber ="123asdf548654",lotNumber ="1231231sdfg23"};
var obj=JsonConvert.SerializeObject(body);
}
example result
{
"qrCode" : "123456adsfg789",
"gtinNumber" : "123asdf548654",
"lotNumber" : "1231231sdfg23"
}
you can add a constructor to your class
public class DocumentDetail
{
public string qrCode { get; set; }
public string gtinNumber { get; set; }
[JsonProperty("lotNumber")] // optional, you can assign any name for serialization
public string lotNumber { get; set; }
[Newtonsoft.Json.JsonConstructor]
public DocumentDetail( string KAREKOD,string GTIN, string LOTNUMBER)
{
qrCode=KAREKOD;
gtinNumber=GTIN;
lotNumber=LOTNUMBER;
}
public DocumentDetail() {}
}
and you don't need to include all properties in the constructor, just include the properties that need different names for a serialization and a deserialiazation.
This worked for me
public class Foo
{
private string bar;
public string IncomingBar { set { bar = value; } }
public string OutgoingBar { get { return bar; } }
}

C#. Universal desition for storing different types

I'm looking for the best approach of working with different types identically.
I have a web service that goes to specific resource, makes some research and returns an object WebResult, that contains all information about completed operations.
And now I'd like to build a set of different metrics, that will describe all received results. These metrics should provide
different types of data
easy way to collect it
possibility to deserialize it.
Example 1
First I've created separate classes for different metrics
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string ExceptionMessage { get; set; }
public abstract void FillAllMetrics(WebResult result);
}
public class Resource1Statistic : AbstractStatistic
{
public string Title { get; set; }
public string[] Table1_Header { get; set; }
public int Table1_RowCount { get; set; }
public string[] Table2_Header { get; set; }
public int Table2_RowCount { get; set; }
public override void FillAllMetrics(WebResult result)
{
this.Url = result.url;
this.Title = result.data["title"];
this.Table1_Header = result.data["table1.header"].ToObject<string[]>();
//...
}
}
It works, but I'd like to make it in more standard way. One of the reason is that in this approach I have to create separate web form for each metrics.
Example 2
Second working example is universal but redundant: create an abstraction of any datatype
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public Dictionary<string, Metric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public class Metric // Specific class for data
{
public string StringValue { get; set; }
public int? IntegerValue { get; set; }
public string[] ArrayValue { get; set; }
public DateTime? DateTimeValue { get; set; }
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add("title",
new Metric() { StringValue = result.data["title"].ToString() });
this.Metrics.Add("Table1 Header",
new Metric() { ArrayValue = result.data["table1.header"].ToObject<string[]>() });
//...
}
It works, but I'm sure there is more elegant solution. I don't like to take all these null values in json.
Examples 3
Generic solution (regarding to Adwaenyth)
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public List<AbstractMetric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public abstract class AbstractMetric{}
public class Metric<T> : AbstractMetric
{
public string Name { get; set; }
public T Value { get; set; }
public string Type { get; private set; }
public Metric()
{
this.Type = typeof(T).ToString();
}
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add(new Metric<string>()
{ Name = "title",
Value = result.data["title"].ToString() });
this.Metrics.Add(new Metric<string[]>()
{ Name = "Table1 Header",
Value = result.data["table1.header"].ToObject<string[]>() });
//...
}
This solution looks nice, but I have to write custom deserializer.
What do you think, is there some good pattern that fits to my task? Or what's the best approach?

Deserialise JSON containing numeric key with Json.NET

I would like to deserialize the following JSON (using Json.NET) to an object, but cannot, as the class name would need to begin with a number.
An example of this is the Wikipedia article API. Using the API to provide a JSON response returns something like this. Note the "16689396" inside the "pages" key.
{
"batchcomplete":"",
"continue":{
"grncontinue":"0.893378504602|0.893378998188|35714269|0",
"continue":"grncontinue||"
},
"query":{
"pages":{
"16689396":{
"pageid":16689396,
"ns":0,
"title":"Jalan Juru",
"extract":"<p><b>Jalan Juru</b> (Penang state road <i>P176</i>) is a major road in Penang, Malaysia.</p>\n\n<h2><span id=\"List_of_junctions\">List of junctions</span></h2>\n<p></p>\n<p><br></p>"
}
}
}
}
How could I deserialize this JSON containing a number which changes based on the article?
It sounds like the Pages property in your Query class would just need to be a Dictionary<int, Page> or Dictionary<string, Page>.
Complete example with the JSON you've provided - I've had to guess at some of the name meanings:
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
public class Root
{
[JsonProperty("batchcomplete")]
public string BatchComplete { get; set; }
[JsonProperty("continue")]
public Continuation Continuation { get; set; }
[JsonProperty("query")]
public Query Query { get; set; }
}
public class Continuation
{
[JsonProperty("grncontinue")]
public string GrnContinue { get; set; }
[JsonProperty("continue")]
public string Continue { get; set; }
}
public class Query
{
[JsonProperty("pages")]
public Dictionary<int, Page> Pages { get; set; }
}
public class Page
{
[JsonProperty("pageid")]
public int Id { get; set; }
[JsonProperty("ns")]
public int Ns { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("extract")]
public string Extract { get; set; }
}
class Test
{
static void Main()
{
string text = File.ReadAllText("test.json");
var root = JsonConvert.DeserializeObject<Root>(text);
Console.WriteLine(root.Query.Pages[16689396].Title);
}
}
Related question: Json deserialize from wikipedia api with c#
Essentially you need to changes from using a class for the pages to a dictionary, which allows for the dynamic nature of the naming convention.
Class definitions :
public class pageval
{
public int pageid { get; set; }
public int ns { get; set; }
public string title { get; set; }
public string extract { get; set; }
}
public class Query
{
public Dictionary<string, pageval> pages { get; set; }
}
public class Limits
{
public int extracts { get; set; }
}
public class RootObject
{
public string batchcomplete { get; set; }
public Query query { get; set; }
public Limits limits { get; set; }
}
Deserialization :
var root = JsonConvert.DeserializeObject<RootObject>(__YOUR_JSON_HERE__);
var page = responseJson.query.pages["16689396"];
You can implement your own DeSerializer or editing the JSON before you DeSerialize it.

Xml Serialization for list Property doesn't work when one item in the list

<TotalRecords ItineraryCount='1' >
<Recs ItineraryNumber="1" >
<Amount>516.6</Amount>
<TravelTime>940</TravelTime>
<FSegment>
<OutProperty>
<Segment No="1">
<Name>Ronald</Name>
<City>London</City>
<Country>United Kingdom</Country>
</Segment>
<Segment No="2">
<Name>Richard</Name>
<City>
London
</City>
<Country>United Kingdom</Country>
</Segment>
</OutProperty>
</FSegment>
</Recs>
</TotalRecords >
I am serializing xml to object of TotalRecords Class. It works fine when there are more than one segment in the OutProperty but in case of one segment it doesn't serialize into list property.
I have also tried with [XmlArray("")] and [XMlArrayItem("")] but it doesn't work. Anyone have idea?
public class TotalRecords
{
public Recs recs { get; set; }
public string ItineraryCount { get; set; }
}
public partial class Recs
{
public string amountField { get; set; }
public string travelTimeField { get; set; }
public FSegment fSegmentField { get; set; }
public string itineraryNumberField { get; set; }
}
public class FSegment
{
public List<Segment> OutProperty {get;set;}
}
public class Segment
{
public string nameField { get; set; }
public string cityField { get; set; }
public string countryField { get; set; }
}
Try to use the following for your Classes and their Properties:
[DataContract]
public class Contact
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
Hacking like this helps in many cases, perhaps in your too (converting List to array):
public class FSegment
{
[XmlIgnore]
public List<Segment> OutProperty {get;set;}
[XmlArray("OutProperty");
public Segment[] _OutProperty
{
get { return OutProperty.ToArray(); }
set { OutProperty = new List<Segment>(value); }
}
}
I am not exactly sure how your provided class definition worked previously without any XML attribute/element definitions since your variable names are not the same as the XML identifiers, meaning that the serialization wouldn't populate the properties that it couldn't find a name for.
Nether the less, the main issue here is that you are trying to put a list into a normal property.
From the XML provided, OutProperty is a single sub element of FSegment and Segment is an array of sub elements of OutProperty, but in your provided code, you tried to make Segment an array sub element of FSegment.
The correct structure of the class definition should be as follows (also including the XML definitions)
[System.Xml.Serialization.XmlRoot(ElementName="TotalRecords")]
public class TotalRecords
{
[System.Xml.Serialization.XmlElement(ElementName="Recs")]
public Recs recs { get; set; }
[System.Xml.Serialization.XmlAttribute(AttributeName = "ItineraryCount")]
public string ItineraryCount { get; set; }
}
public partial class Recs
{
[System.Xml.Serialization.XmlElement(ElementName = "Amount")]
public string amountField { get; set; }
[System.Xml.Serialization.XmlElement(ElementName = "TravelTime")]
public string travelTimeField { get; set; }
[System.Xml.Serialization.XmlElement(ElementName = "FSegment")]
public FSegment fSegmentField { get; set; }
[System.Xml.Serialization.XmlAttribute(AttributeName = "ItineraryNumber")]
public string itineraryNumberField { get; set; }
}
public class FSegment
{
[System.Xml.Serialization.XmlElement(ElementName = "OutProperty")]
public SegmentList OutProperty { get; set; }
}
public class SegmentList
{
[System.Xml.Serialization.XmlElement(ElementName = "Segment")]
public List<Segment> segmentField { get; set; }
}
public class Segment
{
[System.Xml.Serialization.XmlElement(ElementName = "Name")]
public string nameField { get; set; }
[System.Xml.Serialization.XmlElement(ElementName = "City")]
public string cityField { get; set; }
[System.Xml.Serialization.XmlElement(ElementName = "Country")]
public string countryField { get; set; }
[System.Xml.Serialization.XmlAttribute(AttributeName = "No")]
public int segmentNoField { get; set; }
}
Please note that in the above structure, Segment is a list object under the OutProperty object, which resides under the FSegment object.
Using this class structure to load your XML produces the (assumed) correct data in the created objects.
Using the XML definitions allows you to decouple the actual names of class properties from what they are called in the XML data. This allows you to changes the one or the other without affecting the other one, which is useful when you don't control the creation of the XML data.
If you don't want all the XML definitions in your code, change the names of the classes so that you can name your properties the same as what they are called in the XML file. That will allow you couple everything directly

Serialize Property as Xml Attribute in Element

I have the following class:
[Serializable]
public class SomeModel
{
[XmlElement("SomeStringElementName")]
public string SomeString { get; set; }
[XmlElement("SomeInfoElementName")]
public int SomeInfo { get; set; }
}
Which (when populated with some test data) and Serialized using XmlSerializer.Serialize() results in the following XML:
<SomeModel>
<SomeStringElementName>testData</SomeStringElementName>
<SomeInfoElementName>5</SomeInfoElementName>
</SomeModel>
What I need to have is:
<SomeModel>
<SomeStringElementName Value="testData" />
<SomeInfoElementName Value="5" />
</SomeModel>
Is there a way to specify this as attributes without writing my own custom serialization code?
You will need wrapper classes:
public class SomeIntInfo
{
[XmlAttribute]
public int Value { get; set; }
}
public class SomeStringInfo
{
[XmlAttribute]
public string Value { get; set; }
}
public class SomeModel
{
[XmlElement("SomeStringElementName")]
public SomeStringInfo SomeString { get; set; }
[XmlElement("SomeInfoElementName")]
public SomeIntInfo SomeInfo { get; set; }
}
or a more generic approach if you prefer:
public class SomeInfo<T>
{
[XmlAttribute]
public T Value { get; set; }
}
public class SomeModel
{
[XmlElement("SomeStringElementName")]
public SomeInfo<string> SomeString { get; set; }
[XmlElement("SomeInfoElementName")]
public SomeInfo<int> SomeInfo { get; set; }
}
And then:
class Program
{
static void Main()
{
var model = new SomeModel
{
SomeString = new SomeInfo<string> { Value = "testData" },
SomeInfo = new SomeInfo<int> { Value = 5 }
};
var serializer = new XmlSerializer(model.GetType());
serializer.Serialize(Console.Out, model);
}
}
will produce:
<?xml version="1.0" encoding="ibm850"?>
<SomeModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SomeStringElementName Value="testData" />
<SomeInfoElementName Value="5" />
</SomeModel>
Kind of, use the XmlAttribute instead of XmlElement, but it won't look like what you want. It will look like the following:
<SomeModel SomeStringElementName="testData">
</SomeModel>
The only way I can think of to achieve what you want (natively) would be to have properties pointing to objects named SomeStringElementName and SomeInfoElementName where the class contained a single getter named "value". You could take this one step further and use DataContractSerializer so that the wrapper classes can be private. XmlSerializer won't read private properties.
// TODO: make the class generic so that an int or string can be used.
[Serializable]
public class SerializationClass
{
public SerializationClass(string value)
{
this.Value = value;
}
[XmlAttribute("value")]
public string Value { get; }
}
[Serializable]
public class SomeModel
{
[XmlIgnore]
public string SomeString { get; set; }
[XmlIgnore]
public int SomeInfo { get; set; }
[XmlElement]
public SerializationClass SomeStringElementName
{
get { return new SerializationClass(this.SomeString); }
}
}

Categories