How to deserialize interfaces with Newtonsoft Json.Net - c#

I have this class hierarchy :
public class ProxyBotsSnapshotLogEntryDetails : IBotsSnapshotLogEntryDetails
{
public ICollection<IBotSnapshot> Snapshots { get; set; }
}
public class ProxyBotSnapshot : IBotSnapshot
{
public string Name { get; set; }
public ICollection<IBotSnapshotItem> States { get; set; }
}
public class ProxyBotSnapshotItem : IBotSnapshotItem
{
public int Count { get; set; }
public IrcBotChannelStateEnum State { get; set; }
}
and their corresponding interfaces
public interface IBotsSnapshotLogEntryDetails
{
ICollection<IBotSnapshot> Snapshots { get; set; }
}
public interface IBotSnapshot
{
string Name { get; set; }
ICollection<IBotSnapshotItem> States { get; set; }
}
public interface IBotSnapshotItem
{
int Count { get; set; }
IrcBotChannelStateEnum State { get; set; }
}
that I would like to deserialize from JSON:
var test = JsonConvert.DeserializeObject<ProxyBotsSnapshotLogEntryDetails>(entry.DetailsSerialized);
but I get an error saying that Newtonsoft cannot convert interfaces.
I found this promising article:
https://www.c-sharpcorner.com/UploadFile/20c06b/deserializing-interface-properties-with-json-net/
but am not sure how to use the attribute, since in my case, the property is a list of interface.

The converter provided in the article works super nicely, I was just missing the syntax to use it on a collection property. Here is the code with the converter and the working attributes:
// From the article
public class ConcreteConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
public class ProxyBotsSnapshotLogEntryDetails : IBotsSnapshotLogEntryDetails
{
[JsonProperty(ItemConverterType = typeof(ConcreteConverter<ProxyBotSnapshot>))]
public ICollection<IBotSnapshot> Snapshots { get; set; }
}
public class ProxyBotSnapshot : IBotSnapshot
{
public string Name { get; set; }
[JsonProperty(ItemConverterType = typeof(ConcreteConverter<ProxyBotSnapshotItem>))]
public ICollection<IBotSnapshotItem> States { get; set; }
}
public class ProxyBotSnapshotItem : IBotSnapshotItem
{
public int Count { get; set; }
public IrcBotChannelStateEnum State { get; set; }
}

Maybe it works if you add the following settings to the Serializer and Deserializer methods:
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}
For example:
var test = JsonConvert.DeserializeObject<ProxyBotsSnapshotLogEntryDetails>(entry.DetailsSerialized,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});

Related

Custom model binding - ASP.NET Core

I have this incoming request with this payload
{
count : 10,
supplier : {
id : 342,
name : 'test'
},
title : 'some title'
}
and I have this model in my c# code
class SomeModel
{
public string Title { get; set; }
public int SupplierId { get; set; }
public double Amount { get; set; }
}
This is my controller method
public IActionResult Foo(SomeModel data)
{
//...
}
I would like to map the property count in request payload to Amount property in my c# model and mapping the value of supplier.id into SupplierId.
I'm using Newtonsoft Json.NET library for binding
Obviously the simplest way is to create a class corresponding to payload stucture like this
public class SomeModel
{
public string Title { get; set; }
public double Count { get; set; }
public Supplier Supplier { get; set; }
}
public class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
}
Another iteration could be using JsonProperty attribute for Amount and SupplierId property making use of Supplier
class SomeModel
{
public string Title { get; set; }
[JsonProperty("count")]
public double Amount { get; set; }
public int SupplierId => Supplier.Id;
public Supplier Supplier { get; set; }
}
class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
}
But if you like to stick with your current model you will need to create a custom converter. And what I can suggest you
public class NestedPropertyConverter : JsonConverter
{
private string[] _properties;
public NestedPropertyConverter(string propertyChain)
{
//todo: check if property chain has valid structure
_properties = propertyChain.Split('.');
}
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = (JToken)serializer.Deserialize(reader);
foreach (string property in _properties)
{
token = token[property];
if (token == null) //if property doesn't exist
return existingValue; //or throw exception
}
return token.ToObject(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Basically, the converter allows to bind nested properties. Usage
class SomeModel
{
public string Title { get; set; }
[JsonConverter(typeof(NestedPropertyConverter), "id")]
[JsonProperty("supplier")] //its necessary to specify top level property name
public int SupplierId { get; set; }
[JsonProperty("count")]
public double Amount { get; set; }
}
Note
I also tested the following payload
{
count : 10,
supplier : {
id : 342,
name : "test",
test: {
val: 55
}
},
title : "some title"
}
and config for property
[JsonConverter(typeof(NestedPropertyConverter), "test.val")]
[JsonProperty("supplier")]
public int SupplierId { get; set; }
and it works fine.

C# string array from JSON

I'm currently using Newtonsoft's json library to deserialize json from a file.
JSON:
{
"name":"test",
"description":"test",
"tags":"Maps"
}
C#:
public class RootObject
{
public string name { get; set; }
public string tag { get; set; }
public string description { get; set; }
public string[] tags { get; set; }
}
The "tags" has to be an array as it is required for steam.
How can I retrieve the "tags" as an string array?
The console prints out this expection:
Newtonsoft.Json.JsonSerializationException: Error converting value "Maps" to type 'System.String[]'. Path 'tags', line 4, position 17
Your json property "tags" doesn't contain an array, but just a string. Change "Maps" to ["Maps"].
What you need is a Custom JsonConverter.
public class StringOrArrayToStringConveter<T> : JsonConverter
The meat of the solution is the override of the ReadJson which you need to return an array of T. Then you can use it like
[JsonConverter(typeof(StringOrArrayToStringConveter<string>))]
public string[] tags { get; set; }
Hope that put you in a good starting point.
EDIT:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ConsoleApp1
{
public class RootObject
{
public string name { get; set; }
public string tag { get; set; }
public string description { get; set; }
[JsonConverter(typeof(StringOrArrayToStringConveter<string>))]
public string[] tags { get; set; }
}
class Program
{
static void Main(string[] args)
{
var data1 = "{\"name\":\"test\", \"description\":\"test\",\"tags\":\"Maps\"}";
var deserialized1 = JsonConvert.DeserializeObject<RootObject>(data1);
var data2 = "{\"name\":\"test\", \"description\":\"test\",\"tags\":[\"Maps\", \"Maps2\"]}";
var deserialized2 = JsonConvert.DeserializeObject<RootObject>(data2);
}
}
public class StringOrArrayToStringConveter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object returnValue = new Object();
if (reader.TokenType == JsonToken.StartArray)
{
returnValue = JToken.Load(reader).ToObject<T[]>();
}
else if (reader.TokenType == JsonToken.String)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
returnValue = new List<T>() { instance }.ToArray();
}
return returnValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
I would suggest you change Tag into Dictionary e.g. public Dictionary tags { get; set; }
public class RootObject
{
public string name { get; set; }
public string tag { get; set; }
public string description { get; set; }
public string[] tags { get; set; }
public Dictionary<string, Tag> tags { get; set; }
}
public Tag
{
public string name{ get; set; }
}

Custom JSon convertion to C# objects

I am getting JSon from a third party API which does not match my classes.
Some JSon properties are not be be converted others has different names, etc.
How can I define a custom conversion from the JSON to my C# object.
These are the objects I have in C#:
public class PropertyList {
public Int32 Page { get; set; }
public String[] ErrorMessages { get; set; }
public List<Property> Properties { get; set; }
}
public class Property {
public String Name { get; set; }
public String Reference { get; set; }
public String Region { get; set; }
public IList<String> Features { get; set; }
public String Id { get; set; }
public Double Price { get; set; }
public IList<String> ImagesUrls { get; set; }
}
And this is the JSon data which I want to convert from:
{
"page" : 0,
"errorMessages" : [ ],
"listings" : [
{
"data" : {
"name" : "The name",
"reference__c" : "ref1234",
"region__c" : "London",
"features__c" : "Garage;Garden;",
"id" : "id1234",
"price_pb__c" : 700000,
},
"media" : {
"images" : [
{
"title" : "image1",
"url" : "http://www.domain.com/image1"
},
{
"title" : "image2",
"url" : "http://www.domain.com/image2"
}
]
}
}
{
NOTE: Other items
}
]
}
How should I do this?
UPDATE 1
Using Thiago suggestion I was able to parse Page and ErrorMessages but not Properties. So I create the following converter:
public class PropertyResultConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return (objectType == typeof(PropertyResult));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
PropertyResult propertyResult = jo.ToObject<PropertyResult>();
propertyResult.Properties = jo.SelectToken("listings.data").ToObject<List<Property>>();
return propertyResult;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
But when I try this I get the following error:
An exception of type 'System.NullReferenceException' occurred
On:
propertyResult.Properties = jo.SelectToken("listings.data").ToObject<List<Property>>();
Any idea why this happens?
Miguel, have you tried http://www.newtonsoft.com/json ?
You can do the mappings of your code using C# attributes, like the snippets below.
public class PropertyList {
[JsonProperty("page")]
public Int32 Page { get; set; }
[JsonProperty("errorMessages")]
public String[] ErrorMessages { get; set; }
[JsonProperty("listings")]
public List<Property> Properties { get; set; }
}
public class Property {
[JsonProperty("name")]
public String Name { get; set; }
[JsonProperty("reference__c")]
public String Reference { get; set; }
[JsonProperty("region__c")]
public String Region { get; set; }
[JsonProperty("features__c")]
public IList<String> Features { get; set; }
[JsonProperty("id")]
public String Id { get; set; }
[JsonProperty("price_pb__c")]
public Double Price { get; set; }
[JsonProperty("media")]
public IList<String> ImagesUrls { get; set; }
}
I'm not sure if IList will work as you are expecting. I believe you will need to adapt it to Dictionary.
You can take a look on the API:
http://www.newtonsoft.com/json/help/html/SerializationAttributes.htm
UPDATE
Try to parse 'data' using the snippet below:
JObject jsonObject = JObject.Parse(json);
IList<JToken> results = jsonObject["listings"]["data"].Children().ToList();
IList<Property> processedList = new List<Property>();
foreach (JToken item in results)
{
Property propertyItem = JsonConvert.DeserializeObject<Property>(item.ToString());
processedList.Add(propertyItem);
}
I ran that snippet and it worked. You can explore the code that I generated on my github:
https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/ParsingJson.cs

Deserialize Json objects with Json.Net

I'm trying to deserialize an object with Json.net. I was able to do it successfully but its more of a hack so I'm looking for a better/proper way to do so.
{
"page":"admin",
"context":"id",
"records":{
"rCount":6,
"1":[
{
"name":"Romeo",
"dob":"01\/01\/1970"
},
{
"name":"Harry",
"dob":"10\/10\/2012"
},
{
"name":"Lee",
"dob":"17\/10\/2012"
}],
"2":[
{
"name":"Mark",
"dob":"01\/01\/1970"
},
{
"name":"Jack",
"dob":"10\/10\/2012"
},
{
"name":"Json",
"dob":"17\/10\/2012"
}],
}}
this is the json string the issue is with the records object. if it doesn't have that rCount variable then it can be deserialized as a Dictionary but because of the rCount variable it can't be deserialized properly as a dictionary. What should be the proper way of deserialzing this object.
Here is my Solution:
class Program
{
static void Main(string[] args)
{
var recordFile = JsonConvert.DeserializeObject<RecordFile>(Properties.Resources.data);
}
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Records Records { get; set; }
}
public class Records
{
public int RCount { get; set; }
[JsonExtensionData]
private Dictionary<string, object> _reocordList;
public List<Record[]> RecordList
{
get
{
if (_reocordList != null && _reocordList.Count > 0)
{
return _reocordList.Values.Select(record => JsonConvert.DeserializeObject<Record[]>(record.ToString())).ToList();
}
return new List<Record[]>();
}
}
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
}
You can use jObject to manually parse JSON:
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Records Records { get; set; }
}
public class Records
{
public int RCount { get; set; }
public IDictionary<string, List<Record>> RecordsDictionary { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Then:
var jObject = JObject.Parse(\* your json *\);
var recordFile = new RecordFile
{
Page = jObject.Value<string>("page"),
Context = jObject.Value<string>("context"),
Records = new Records
{
RCount = jObject["records"].Value<int>("rCount"),
RecordsDictionary =
jObject["records"].Children<JProperty>()
.Where(prop => prop.Name != "rCount")
.ToDictionary(prop => prop.Name),
prop =>
prop.Value.ToObject<List<Record>>())
}
};
Of course it can be easily to handle cases, when property is not present.
I'm assuming you want to get rid of the Records class completely, ending up with something like this:
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Dictionary<string, Record[]> Records { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Since you don't care at all about the records.rCount property from the JSON, you could specify a new error handler to simply ignore the property:
var recordFile = JsonConvert.DeserializeObject<RecordFile>(
jsonString,
new JsonSerializerSettings
{
Error = (sender, args) =>
{
if (args.ErrorContext.Path == "records.rCount")
{
// Ignore the error
args.ErrorContext.Handled = true;
}
}
});
Now when an error is encountered with the property in question, the deserializer will just skip over it. Another option would be to write a custom converter, but that feels like overkill just to ignore one property.
Example: https://dotnetfiddle.net/3svPqk
The other answers posted so far should both work. For completeness, I'll show how you can use a custom JsonConverter to solve this and also simplify your model a bit.
Here is the code for the converter:
class RecordFileConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(RecordFile));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
RecordFile rf = new RecordFile();
rf.Page = (string)jo["page"];
rf.Context = (string)jo["context"];
JObject records = (JObject)jo["records"];
rf.RecordCount = (int)records["rCount"];
rf.Records = records.Properties()
.Where(p => p.Name != "rCount")
.Select(p => p.Value.ToObject<Record[]>())
.ToList();
return rf;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To work with the converter, change your model classes as shown below. (Notice that the RecordFile class has a JsonConverter attribute to link the custom RecordFileConverter to it. Also notice the Records class is deleted, while the RCount and RecordList properties have been moved up to the RecordFile class and renamed.)
[JsonConverter(typeof(RecordFileConverter))]
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public int RecordCount { get; set; }
public List<Record[]> Records { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Then, deserialize as normal:
var recordFile = JsonConvert.DeserializeObject<RecordFile>(json);
Demo: https://dotnetfiddle.net/jz3zUT

deserialize array of multi data type

I have problem when trying to deserialize a json from internet.
But I don't know how to deserialize difference types in one array correctly.
How to do it correctly?
Sorry for my poor english.
Here is json:
{
"timestamp":"2012-06-19T08:00:49Z",
"items":[
{
"type":"text",
"content":"etc"
},
{
"type":"video",
"url":"etc"
}
...
]
}
My code:
public interface IPost
{
string PostType { get; set; }
}
public class TextPost : IPost
{
[JsonProperty("type")]
public string PostType { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
}
public class VideoPost : IPost
{
[JsonProperty("type")]
public string PostType { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
}
public class ResponseData
{
[JsonProperty("timestamp")]
public string Timestamp { get; set; }
[JsonProperty("items")]
public List<IPost> Items { get; set; }
}
I figured out a way to parse it. By using JsonConverter.
Here is my Converter class:
public class PostsConverter : JsonConverter
{
// Create an instance
protected IPost Create(JObject jObject)
{
if (PostType("Text", jObject)) return new Text();
if (PostType("Video", jObject)) return new Video();
throw new Exception("Error");
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var list = new List<IPost>();
var arr = JArray.Load(reader);
foreach (var item in arr)
{
var obj = JObject.Load(item.CreateReader());
// Create target object based on JObject
var post = Create(obj);
// Populate the object properties
serializer.Populate(obj.CreateReader(), post);
list.Add(post);
}
return list;
}
private bool PostType(string type, JObject jObject)
{
return jObject["PostType"] != null && jObject["PostType"].Value<string>() == type;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// I don't need serialize object
throw new NotImplementedException();
}
}
Response class:
public class ResponseData
{
[JsonProperty("timestamp")]
public string Timestamp { get; set; }
[JsonConverter(typeof(PostsConverter))]
[JsonProperty("items")]
public List<IPost> Items { get; set; }
}
Now I can parse it:
var myData = JsonConvert.DeserializeObject<ResponseData>(json);

Categories

Resources