deserialize array of multi data type - c#

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);

Related

How to deserialize interfaces with Newtonsoft Json.Net

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
});

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; }
}

Handling JSON single object and array

I'm using Newtonsoft.Json to work with some JSON data that is being returned to me. Depending on what I request I can either get back something that looks like:
{
"TotalRecords":2,
"Result":
[
{
"Id":24379,
"AccountName":"foo"
},
{
"Id":37209,
"AccountName":"bar"
}
],
"ResponseCode":0,
"Status":"OK",
"Error":"None"
}
or
{
"Result":
{
"Id":24379,
"AccountName":"foo"
},
"ResponseCode":0,
"Status":"OK",
"Error":"None"
}
So sometimes "Result" is an array of Results or "Result" could be a single response.
I've tried using the answer from How to handle both a single item and an array for the same property using JSON.net but I still get errors.
In particular I'm getting a
Newtonsoft.json.jsonSerializationException: 'Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List'...
Custom converter looks like:
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objecType)
{
return (objecType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objecType, object existingValue,
JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
My response class(es) look like
public class TestResponse
{
[JsonProperty("Result")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<DeserializedResult> Result { get; set; }
}
public class DeserializedResult
{
public string Id { get; set; }
public string AccountName { get; set; }
}
And finally my request looks like
List<TestResponse> list = JsonConvert.DeserializeObject<List<TestResponse>>(response.Content);
Your code is fine, it just needs a few type tweaks.
This line
List<TestResponse> list = JsonConvert.DeserializeObject<List<TestResponse>>(response.Content);
needs to be like this, because your response is an object, not a List.
TestResponse list = JsonConvert.DeserializeObject<TestResponse>(response);
Then your custom deserializer attribute:
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
needs to become:
[JsonConverter(typeof(SingleOrArrayConverter<DeserializedResult>))]
because your Result object is not a string or an array of strings, it's either an array of DeserializedResults or a DeserializedResult.
I think, that there is no way to understend which type of response do you have due desirialization. Thats why i propose to check manualy type of response:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace TestConsoleApp
{
public class Class1
{
public class Result
{
public int Id { get; set; }
public string AccountName { get; set; }
}
public class ModelWithArray
{
public int TotalRecords { get; set; }
public List<Result> Result { get; set; }
public int ResponseCode { get; set; }
public string Status { get; set; }
public string Error { get; set; }
}
public class Result2
{
public int Id { get; set; }
public string AccountName { get; set; }
}
public class ModelWithoutArray
{
public Result2 Result { get; set; }
public int ResponseCode { get; set; }
public string Status { get; set; }
public string Error { get; set; }
}
public static void Main(params string[] args)
{
//string json = "{\"TotalRecords\":2,\"Result\":[{\"Id\":24379,\"AccountName\":\"foo\"},{\"Id\":37209,\"AccountName\":\"bar\"}], \"ResponseCode\":0,\"Status\":\"OK\",\"Error\":\"None\"}";
string json = "{\"Result\":{\"Id\":24379,\"AccountName\":\"foo\"},\"ResponseCode\":0,\"Status\":\"OK\",\"Error\":\"None\"}";
if (checkIsArray(json))
{
ModelWithArray data = JsonConver.DeserializeObject<ModelWithArray >(json);
}else
{
ModelWithoutArray data = JsonConver.DeserializeObject<ModelWithoutArray>(json);
}
}
static bool checkIsArray(string json)
{
Dictionary<string, object> desData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
if (desData["Result"].GetType().Name.Contains("Array"))
{
return true;
}
else
{
return false;
}
}
}
}

C# deserialize Json based on condition

{
"timeAgo": "6 minutes ago",
"time": "07/11/2016 07:00 AM",
"alertId": 145928,
"details": {
},
"priority": 10,
"type": 2,
"isClosed": 0,
"notesCount": 0,
"patientAccountId": 680,
"isRead": 0
}
I want to deserialize the json based on the int value 'type', in such as way, I want the details to be different types
public class Notification
{
public string timeAgo { get; set; }
public string time { get; set; }
public int alertId { get; set; }
public object details { get; set; }
public int priority { get; set; }
public int type { get; set; }
public int isClosed { get; set; }
public int notesCount { get; set; }
public int patientAccountId { get; set; }
public int isRead { get; set; }
}
if type = 1, then the object 'details' is of type A, if type = 2, 'details' is of type B and so on. There are about 25 values for type.
So, later I can do something like:
Notification n = ....
if (type == 1)
{
A a = (a) n.details;
If your json does not have appropriate typing included in the JSON, this will work.
This may need tweaking if your actual structure is more complex, but I managed to get this to work on your sample.
var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<Notification>(
js,
new ItemConverter());
public class ItemA : Item { }
public class ItemB : Item { }
public class Item { }
public class Notification
{
public string timeAgo { get; set; }
public string time { get; set; }
public int alertId { get; set; }
public Item details { get; set; }
public int priority { get; set; }
public int type { get; set; }
public int isClosed { get; set; }
public int notesCount { get; set; }
public int patientAccountId { get; set; }
public int isRead { get; set; }
}
public class ItemConverter : JsonConverter
{
private Type currentType;
public override bool CanConvert(Type objectType)
{
return typeof(Item).IsAssignableFrom(objectType) || objectType == typeof(Notification);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
if (item["type"] != null)
{
// save the type for later.
switch (item["type"].Value<int>())
{
case 1:
currentType = typeof(ItemA);
break;
default:
currentType = typeof(ItemB);
break;
}
return item.ToObject<Notification>();
}
// use the last type you read to serialise.
return item.ToObject(currentType);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can use special JSON serializer settings, like
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
Type name will be stored with actual "details" data, so you can deserialize it to valid class.
var instance = new Notification
{
details = new Details
{
Name = "Hello"
}
};
var json = JsonConvert.SerializeObject(instance, jsonSerializerSettings);
Produced json
{"$type":"Application.Notification, Application","details":{"$type":"Application.Details, Application","Name":"Hello"}}
var json = "{\r\n \"timeAgo\": \"6 minutes ago\",\r\n \"time\": \"07/11/2016 07:00 AM\",\r\n \"alertId\": 145928,\r\n \"details\": {\r\n\r\n\r\n },\r\n \"priority\": 10,\r\n \"type\": 2,\r\n \"isClosed\": 0,\r\n \"notesCount\": 0,\r\n \"patientAccountId\": 680,\r\n \"isRead\": 0\r\n }";
Notification data = JsonConvert.DeserializeObject<Notification>(json);
var type=1;
if (type == 1)
{
string[] a = data.details as string[];
}

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

Categories

Resources