json.net deserializing #prefixed properties - c#

I am trying to deserialize json i retrieve from a website into POCOs, and I am stuck on the fact, that json.net will not deserialize properties with a preceding # sign.
I have found numerous posts on SO, which state the solution to this is to annotate the C# properties in the POCO with the JsonPropertyAttribute and specifying the property name directly. I have done so, however, my C# property stays null.
POCO code:
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
public class Event : IEvent
{
private readonly String name;
private readonly String context;
private readonly String type;
private readonly UInt32 id;
private readonly DateTime startDate;
private readonly DateTime endDate;
public Event(String name)
{
this.name = name;
}
[JsonProperty(Required = Required.Always, PropertyName = "#context")]
public String Context { get { return this.context; } }
[JsonProperty(Required = Required.Always, PropertyName = "#type")]
public String Type { get { return this.type; } }
[JsonProperty(Required = Required.Always)]
public String Name { get { return this.name; } }
public UInt32 ID { get { return this.id; } }
public DateTime StartDate { get { return this.startDate; } }
public DateTime EndDate { get { return this.endDate; } }
}
Deserializing code:
public void Test()
{
string innerHtml = #"{
""#context"": ""http:\/\/ schema.org"",
""#type"": ""Event"",
""name"": ""Kabarett Tipps in \u00d6sterreich: Diese K\u00fcnstler sollten Sie nicht verpassen"",
""location"": {
""#type"": ""Place"",
""address"": {
""#type"": ""PostalAddress"",
""addressCountry"": ""AT"",
""addressLocality"": ""Wien - Landstrasse"",
""postalCode"": ""1030"",
""streetAddress"": null
},
""name"": ""Ganz \u00d6sterreich"",
""url"": ""\/l\/ganz-oesterreich""
},
""url"": ""\/e\/kabarett-tipps-in-oesterreich-diese-kuenstler-sollten-sie-nicht-verpassen#st-241664441"",
""startDate"": ""2018-06-18"",
""endDate"": ""2019-06-24"",
""image"": ""https:\/\/cdn.kurier.at\/img\/100\/210\/772\/kabarett.jpg""
}";
IEvent #event = JsonConvert.DeserializeObject<Event>(innerHtml);
}
I see Name being populated, but Type and Context remain null. Anybody else observing this issue?

As Brian Rogers pointed out, my POCO properties were readonly. Json.net's documentation states:
Because a JsonConverter creates a new value, a converter will not work
with readonly properties because there is no way to assign the new
value to the property. Either change the property to have a public
setter or place a JsonPropertyAttribute or DataMemberAttribute on the
property.
Apparently placing that attribute on the property does not have the desired effect. I placed the attribute on the private readonly fields backing the properties, which finally yielded the desired result.
Thanks Brian for the pointer :)

Related

Can't set TimeSpan property from JSON source C#

I have been trying to read a JSON file and deserialize it to a class object. The problem is that I am storing a TimeSpan? property of the class as string in the JSON file which doesn't get deserialized and remains null. Please find the code for reference below:
Interface:
public interface IAirport
{
.
.
.
TimeSpan? TimeOffset { get; set; }
.
.
.
}
Class :
public class Airport:IAirport{
.
.
.
private TimeSpan? _offsetTime;
public TimeSpan? OffssetTime
{
get{return _offsetTime;}
set{SetProperty<TimeSpan>(ref _offsetTime, value);}
}
.
.
.
}
This is the way I deserialize :
private static T ConvertJsonStreamToObject<T>(StreamReader fileStream)
{
JsonSerializer serializer = new JsonSerializer();
return (T)serializer.Deserialize(fileStream, typeof(T));
}
and lastly the JSON :
{
"AirportDesignation": "ZZU",
"EffectiveDate": "1901-01-01 00:00:00.0000000",
"Description": "Mzuzu",
"DescriptionShort": "Mzuzu",
"EffectiveStatus": "A",
"Country": "MWI",
"State": " ",
"LatitudeDegrees": 11,
"LatitudeMinutes": 27,
"LatitudeSeconds": 0,
"LatitudeCoordinates": "S",
"LongitudeDegrees": 34,
"LongitudeMinutes": 1,
"LongitudeSeconds": 0,
"LongitudeCoordinates": "E",
"AirportType": "A",
"TimeOffset": "00:00:00",
"IsTimeOffsetPositive": true
}
I tried the following way to set the property and use TimeSpan.Parse() to parse the string to TimeSpan but none of them worked :
private string _timeOffset;
public TimeSpan? TimeOffset
{
get { return TimeSpan.Parse(_timeOffset); }
set { _timeOffset = value.ToString(); }
}
I am out of ideas to try, any help would be appreciated.
UPDATE:
I tried #Caius Jard suggestion, getting the System.ArgumentNullException
Updated the property as below:
private string _timeOffset;
[Required]
public TimeSpan? TimeOffset
{
get { return TimeSpan.Parse(_timeOffset); }
set { SetProperty<string>(ref _timeOffset, value.Value.ToString()); }
}
Found the culprit finally, It was an attribute causing the problem. I had an interface as the base for the Airport class and it required the TimeOffset to be a [DataMember] otherwise the property isn't identified as the data member of the class.
Adding the [DataMember] attribute now sets the value.

Xml List Serialization and Node Type Names

Ive come across multiple questions and answers on here but none specific to my situation.
I have a class 'Entity' with multiple classes that extend off of it. I want the serialization to hit the list and understand and use the type of each item for the node name.
Now, I can use what is commented out (define each array item in the main class and define the name of such by using [XmlArrayItem("Subclass1", typeof(subclass1)] but I want to keep all definitions in their subclass and I will be having too many subclasses to define everything in the main entity class...Is there anyway to achieve this?
I have tried using [XmlType(TypeName="...")] for the subclasses and so on but that did not work.
[Serializable]
[XmlInclude(typeof(Subclass1))]
[XmlRoot("Entity")]
public class Entity{
[XmlArray("CausedBy")]
//[XmlArrayItem("Subclass1", typeof(subclass1))]
//[XmlArrayItem("Sublcass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
[XmlInclude(typeof(Subclass2))]
public class Subclass1:Entity{
//Code...
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2:Subclass1{
//Code...
}
Serializing the above code after creating an entity and adding a Subclass1 and Subclass2 to the list 'CausedBy' class results in the following:
<Entity>
<CausedBy>
<Entity ... xsi:type="SubClass1" />
<Entity ... xsi:type="SubClass2" />
</CausedBy>
<Entity>
I would like the output to show:
<Entity>
<CausedBy>
<SubClass1 .../>
<SubClass2 .../>
</CausedBy>
<Entity>
Since I totally failed to read the question to begin with, here's a new answer (it's a bit of a tl;dr, so you can always skip to the end and follow the link):
It isn't possible to get the built in serializer class to work because you don't wish to add the attributes that it needs to be able to operate. Your only option is to seralize the class yourself, however, this need not be as tedious as it sounds; I had a similar issue a few years ago with DataGridView in virtual mode and produced a generic virtualizer that could be used to virtualize the data for display; it used a custom attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class showColumnAttribute : System.Attribute
{
///<summary>Optional display format for column</summary>
public string Format;
///<summary>Optional Header string for column<para>Defaults to propety name</para></summary>
public string Title;
///<summary>Optional column edit flag - defaults to false</summary>
public bool ReadOnly;
///<summary>Optional column width</summary>
public int Width;
///<summary>
///Marks public properties that are to be displayed in columns
///</summary>
public showColumnAttribute()
{
Format = String.Empty;
Title = String.Empty;
ReadOnly = false;
Width = 0;
}
}
And a constructor:
///<summary>
///Extracts the properties of the supplied type that are to be displayed
///<para>The type must be a class or an InvalidOperationException will be thrown</para>
///</summary>
public Virtualiser(Type t)
{
if (!t.IsClass)
throw new InvalidOperationException("Supplied type is not a class");
List<VirtualColumnInfo> definedColumns = new List<VirtualColumnInfo>();
PropertyInfo[] ps = t.GetProperties();
MethodInfo mg, ms;
for (int i = 0; i < ps.Length; i++)
{
Object[] attr = ps[i].GetCustomAttributes(true);
if (attr.Length > 0)
{
foreach (var a in attr)
{
showColumnAttribute ca = a as showColumnAttribute;
if (ca != null)
{
mg = ps[i].GetGetMethod();
if (mg != null)
{
ms = ps[i].GetSetMethod();
definedColumns.Add
(
new VirtualColumnInfo
(
ps[i].Name, ca.Width, ca.ReadOnly, ca.Title == String.Empty ? ps[i].Name : ca.Title,
ca.Format, mg, ms
)
);
}
break;
}
}
}
}
if (definedColumns.Count > 0)
columns = definedColumns.ToArray();
}
This extracts the public properties of the class and supplies marked items to the DataGridView as columns together with a header, format, etc.
The effect of all of this (and the rest of the missing code) was that any type could be virtualized in a dataGridView simply by tagging public properties and calling the virtualizer once for a given type:
#region Virtualisation
static readonly Virtualiser Virtual = new Virtualiser(typeof(UserRecord));
[XmlIgnore] // just in case!
public static int ColumnCount { get { return Virtual.ColumnCount; } }
public static VirtualColumnInfo ColumnInfo(int column)
{
return Virtual.ColumnInfo(column);
}
public Object GetItem(int column)
{
return Virtual.GetItem(column, this);
}
/*
** The supplied item should be a string - it is up to this method to supply a valid value to the property
** setter (this is the simplest place to determine what this is and how it can be derived from a string).
*/
public void SetItem(int column, Object item)
{
String v = item as String;
int t = 0;
if (v == null)
return;
switch (Virtual.GetColumnPropertyName(column))
{
case "DisplayNumber":
if (!int.TryParse(v, out t))
t = 0;
item = t;
break;
}
try
{
Virtual.SetItem(column, this, item);
}
catch { }
}
#endregion
The number of columns, their properties and order can be specified automatically by creating a number of public properties derived from the class data:
#region Display columns
[showColumn(ReadOnly = true, Width = 100, Title = "Identification")]
public String DisplayIdent
{
get
{
return ident;
}
set
{
ident = value;
}
}
[showColumn(Width = 70, Title = "Number on Roll")]
public int DisplayNumber
{
get
{
return number;
}
set
{
number = value;
}
}
[showColumn(Width = -100, Title = "Name")]
public string DisplayName
{
get
{
return name == String.Empty ? "??" : name;
}
set
{
name = value;
}
}
#endregion
This would virtualize any class for dataGridView to display and edit data and I used it many times over the years and the extraction of properties to display is exactly what is required for XML serialization, indeed, it has a lot of the same characteristics.
I was going to adapt this method to do the same job for XML serialization but someone has already done it at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=474453, I hope you can make use of this method to solve your problem.
This works for me:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Entity entity = new Entity();
entity.CausedBy = new List<Entity>();
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass1());
entity.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test.txt"));
}
}
[Serializable]
[XmlRoot("Entity")]
public class Entity
{
[XmlArray("CausedBy")]
[XmlArrayItem("SubClass1", typeof(Subclass1))]
[XmlArrayItem("SubClass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
public class Subclass1 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToShortDateString();
public String SubClass1Item { get { return "Test1 " + t; } set { } }
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToString();
public String SubClass2Item { get { return "Test2 " + t; } set { } }
}
It produces:
<Entity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CausedBy>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
</CausedBy>
</Entity>

Deserializing xml to list object returning null

I'm trying to deserialize xml data using xmlreader into a list object but I am getting a null back from my call. Here is a sample of my xml data...
<ExceptionLog>
<ExceptionLogData MessageCount="1" SourceDateTime="2016-02-08T09:32:41.713" MinSourceDateTime="2016-02-08T09:32:41.713" DataId="610029" MaxExceptionLogID="610029" MessageText="INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session. Session not found, missing session hash: hX7K7LONeTilw5RfGT432g==
This is expected, it can happen if the session has expired and swept away, or if the user logs out, or if its just someone trying to hack in. " MachineName="VERTEXDPORTSQL1" AppDomainName="VTMS.Windows.SalesforceServicingAgent.exe" ProcessName="VTMS.Windows.SalesforceServicingAgent" />
<ExceptionLogData MessageCount="1" SourceDateTime="2016-02-08T09:22:39.340" MinSourceDateTime="2016-02-08T09:22:39.340" DataId="610028" MaxExceptionLogID="610028" MessageText="INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session. Session not found, missing session hash: rtZTrLk2f99iVttLoz31tg==
This is expected, it can happen if the session has expired and swept away, or if the user logs out, or if its just someone trying to hack in. " MachineName="VERTEXDPORTSQL1" AppDomainName="VTMS.Windows.SalesforceServicingAgent.exe" ProcessName="VTMS.Windows.SalesforceServicingAgent" />
</ExceptionLog>
This is the object class code that I am trying to create...
public class ExceptionLog {
public ExceptionLog() {
ExceptionLogData = new List<ExceptionLogExceptionLogData>();
}
public List<ExceptionLogExceptionLogData> ExceptionLogData { get; set; }
}
public class ExceptionLogExceptionLogData {
private DateTime _sourceDateTimeField;
private DateTime _minSourceDateTimeField;
private uint dataIdField;
private uint _maxExceptionLogIdField;
private string _messageTextField;
private string _machineNameField;
private string _appDomainNameField;
private string _processNameField;
public byte MessageCount { get; set; }
public DateTime SourceDateTime {
get {
return _sourceDateTimeField;
}
set {
_sourceDateTimeField = value;
}
}
public DateTime MinSourceDateTime {
get {
return _minSourceDateTimeField;
}
set {
_minSourceDateTimeField = value;
}
}
public uint DataId {
get {
return dataIdField;
}
set {
dataIdField = value;
}
}
public uint MaxExceptionLogID {
get {
return _maxExceptionLogIdField;
}
set {
_maxExceptionLogIdField = value;
}
}
public string MessageText {
get {
return _messageTextField;
}
set {
_messageTextField = value;
}
}
public string MachineName {
get {
return _machineNameField;
}
set {
_machineNameField = value;
}
}
public string AppDomainName {
get {
return _appDomainNameField;
}
set {
_appDomainNameField = value;
}
}
public string ProcessName {
get {
return _processNameField;
}
set {
_processNameField = value;
}
}
}
And finally here is how I am trying to deserialize the data...
using (var dataReader = sqlCommand.ExecuteXmlReader())
{
var serializer = new XmlSerializer(typeof(ExceptionLog));
var returnDataList = serializer.Deserialize(dataReader) as List<ExceptionLogExceptionLogData>;
return returnDataList;
}
What have I missed or what am I doing wrong?
I have another approach that I can use until I figure this out and that is the old fashioned way of creating my object list and programmatically populating it with my objects on the fly - not very graceful but for the time being it works.
TIA
XmlSerializer is defined of type ExceptionLog but you're then casting the result to List
var serializer = new XmlSerializer(typeof(ExceptionLog));
var returnDataList = serializer.Deserialize(dataReader) as List<ExceptionLogExceptionLogData>;
The casting should be to the type of serializer:
var returnDataList = serializer.Deserialize(dataReader) as ExceptionLog;
I didn't check all the elements but you should also mark ExceptionLogData with XmlElement attribute.
[XmlElement]
public List<ExceptionLogExceptionLogData> ExceptionLogData { get; set; }
There might be some issues with the other properties but this should address the problem in the question

C# JSON deserializer does only check format, it doesn't care about object data

I want to deserialize a JSON message received from TCP/IP in my C# application. I have really simple classes with one or two Properties at most, but they are derived. For example:
public class SemiCommand
{
public SemiCommand()
{
UID = string.Empty;
}
public SemiCommand(string uid)
{
UID = uid;
}
public string UID
{
get;
set;
}
public new string ToString()
{
return new JavaScriptSerializer().Serialize(this);
}
}
public class ResponseCommand : SemiCommand
{
protected const string DEFAULT_INFO = "OK";
public ResponseCommand()
{
UID = string.Empty;
Info = DEFAULT_INFO;
}
public ResponseCommand(string uid)
{
UID = uid;
Info = DEFAULT_INFO;
}
public string Response
{
get;
set;
}
public string Info
{
get;
set;
}
public class GetInfoResponseCommand : ResponseCommand
{
public GetInfoResponseCommand()
: base()
{
Response = "GetInfoResponse";
}
public List<ClientProcess> Processes
{
get;
set;
}
}
I made a few attempts with DataMember and DataContract attributes to solve my problem, but it didn't fix it.
The problem lies here:
private Type[] _deserializationTypes = new Type[] { typeof(StartProcessesRequestCommand), typeof(RequestCommand) };
public RequestCommand TranslateTCPMessageToCommand(string messageInString)
{
RequestCommand requestCommand = null;
for (int i = 0; i < _deserializationTypes.Length; i++)
{
try
{
requestCommand = TryDeserialize(_deserializationTypes[i], messageInString);
break;
}
catch
{
}
}
if (requestCommand == null || (requestCommand != null && requestCommand.Command == string.Empty))
{
throw new System.Runtime.Serialization.SerializationException("Unable to deserialize received message");
}
else
{
return requestCommand;
}
}
If i want to deserialize with a proper format, containing all the necessary properties, it works:
This works: {"Response":"SomeResponse","Info":"Information","UID":"123"}
However, this also works: {"nonexistingProperty":"value"}
The second one also creates a RequestCommand with property values set to null.
1st question: How can I force my messagetranslator function to make RequestCommand only if it receives all of the required properties
2nd question: If i have multiple command types which are derived from one or more ancestors, how is it possible, to deserialize it automatically from the "deepest" class if the received properties allow it.
EDIT:
I serialize/deserialize with these two
private RequestCommand TryDeserialize(Type type, string messageInString)
{
JavaScriptSerializer js = new JavaScriptSerializer();
return (RequestCommand)js.Deserialize(messageInString, type);
}
public string TranslateCommandToTCPMessage(ResponseCommand command)
{
JavaScriptSerializer js = new JavaScriptSerializer();
return js.Serialize(command);
}
The break in the try catch supposed to end the loop if the TrySerialize doesn't throw an exception, therefore the deserialization was successful.
If you actually used DataContractJsonSerializer, and you decorated the members / types in question with DataMember / DataContract, you can actually get this to happen. To do this, you can decorate ALL data members with [IsRequired=true]. This would throw an exception if the members weren't present on the wire.
Another option you have is to use IObjectReference, which let you translate one object to another post-serialization, and you can do this depending on what is deserialized from the wire.

Display List contents specific field in a ComboBox (C#)

Well, my question is a bit silly, but I've tried lots of different thing without result.
I have a ComboBox in my main form and I want to point its data source to the public readonly List PriceChanges list declared in the Filters class. No problem with that but I want to list the Description field.
I tried to assign the "Description" string to the DisplayMember attribute without success. My ComboBox only lists: "BusinessLogic.PriceChange" for each entry, where BusinessLogic is the name of my Namespace and PriceChange the class.
I appreciate any help.
Regards
That's part of the code of my main form
public mainFrm()
{
InitializeComponent();
prodFilter = new Filters();
cbPriceChanges.DataSource = prodFilter.PriceChanges;
cbPriceChanges.DisplayMember = "Description"
}
That's is part of the code that declares the List object
public enum PriceChangeTypes
{
No_Change,
Increased,
Decreased,
All
}
public class PriceChange
{
public String Description;
public readonly PriceChangeTypes Type;
public delegate bool ComparisonFuntionDelegate(Decimal a);
public readonly ComparisonFuntionDelegate ComparisonFunction;
public PriceChange(String Description, PriceChangeTypes type , ComparisonFuntionDelegate CompFunc)
{
this.Description = Description;
Type = type;
ComparisonFunction = CompFunc;
}
}
public class Filters
{
public readonly List<PriceChange> PriceChanges = null;
public Filters()
{
PriceChanges = new List<PriceChange>();
PriceChanges.Add(new PriceChange("No Change", PriceChangeTypes.No_Change, PriceChange => PriceChange == 0));
PriceChanges.Add(new PriceChange("Increased", PriceChangeTypes.Increased, PriceChange => PriceChange > 0));
PriceChanges.Add(new PriceChange("Decreased", PriceChangeTypes.Decreased, PriceChange => PriceChange < 0));
PriceChanges.Add(new PriceChange("All", PriceChangeTypes.All, a => true));
}
}
Have you tried making "Description" a property? It will change a lot in case the list tries to get the field through reflection (as it most likely does).
public class PriceChange {
public string Description{
get;
set;
}
// ...
}
Try adding this to your class :
public override string ToString()
{
return Description;
}
Currently you're just getting the default value of ToString, which is the object namespace and class

Categories

Resources