Deserialize a byte array containing a dictionary with JSON.Net [duplicate] - c#

This question already has answers here:
Newtonsoft Json Deserialize Dictionary as Key/Value list from DataContractJsonSerializer
(3 answers)
Serialize dictionary as array (of key value pairs)
(6 answers)
Serialize Dictionary<,> as array in Json.NET [duplicate]
(2 answers)
Closed 4 years ago.
I am facing the following scenario. A project of mine is throwing an event that contains the following object:
public class MyEvent : BaseEvent
{
public long Id { get; set; }
public Dictionary<string, long> Pairs { get; set; }
}
I received the event and read the data as byte[] on my receiver side. The current code I have to read any generic event is:
public static T Decode(byte[] data)
{
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
using (var stream = new MemoryStream(data))
{
using (var sr = new StreamReader(stream, Encoding.UTF8))
{
var jr = new JsonTextReader(sr);
var aux = Encoding.UTF8.GetString(data);
return serializer.Deserialize(jr, typeof(T)) as T;
}
}
}
where T is my class MyEvent . Unfortunately the thrown exception is:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,System.Int64]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'OperationTimePairs', line 1, position 61.
The way I read it is that the object received doesn't have the correct format.. however if I try to read it through var aux = Encoding.UTF8.GetString(data); I can see that the structure is the correct one. Any idea how can I fix this? Thanks!
EDIT:
Json Example:
{
"Timestamp":"\/Date(1540996292134)\/",
"Pairs":[
{
"Key":"first time",
"Value":28
},
{
"Key":"second time",
"Value":30
},
{
"Key":"third time",
"Value":101
},
{
"Key":"operation time",
"Value":231
}
],
"Id":123637
}

I think that your classes don't match the json string structure.
Given the following json string:
{
"Timestamp":"\/Date(1540996292134)\/",
"Pairs":[
{
"Key":"first time",
"Value":28
},
{
"Key":"second time",
"Value":30
},
{
"Key":"third time",
"Value":101
},
{
"Key":"operation time",
"Value":231
}
],
"Id":123637
}
You can change your models to match the json structure, something like this:
public class MyEvent : BaseEvent
{
public long Id { get; set; }
public List<KeyValuePair<string, long>> Pairs { get; set; }
[JsonIgnore]
public Dictionary<string, long> PairsDictionary
{
get
{
if (Pairs == null)
{
return new Dictionary<string, long>();
}
return Pairs.ToDictionary(pair => pair.Key, pair => pair.Value);
}
}
}
public class BaseEvent
{
public DateTime Timestamp { get; set; }
}
Please note:
PairsDictionary is a non-serializable property based on Pairs
Given that you didn't provide the class definition of BaseEvent, I will assume that it has 1 property only
Testing the deserialization:
string json = #"{
""Timestamp"":""\/Date(1540996292134)\/"",
""Pairs"":[
{
""Key"":""first time"",
""Value"":28
},
{
""Key"":""second time"",
""Value"":30
},
{
""Key"":""third time"",
""Value"":101
},
{
""Key"":""operation time"",
""Value"":231
}
],
""Id"":123637
}";
MyEvent eventData = JsonConvert.DeserializeObject<MyEvent>(json);
Or as an alternative (using generics):
T data = JsonConvert.DeserializeObject(json, typeof(T)) as T;

Related

Deserialize array in JSON into class/object

I'm trying to store JSON data into a class. I could deserialize my otherJSON string into class by: var ser = JsonConvert.DeserializeObject<ClsResult>(myJSON); before I got stuck with array.
{
\"Test\": [{
\"FirstBool\":1,
\"aString\":\"hello\"
}]
}
This is my class for JSON:
public class Test
{
[JsonProperty("FirstBool")]
public bool FirstBool { get; set; }
[JsonProperty("aString")]
public string aString { get; set; }
}
public class ResultObject
{
[JsonProperty("Test")]
public List<Test> Test { get; set; }
}
How I deserialize my non-array JSON:
var ser = JsonConvert.DeserializeObject<ResultObject>(myJSON);
What changes do I need to make it work again?
Edited answer
Your json string as I've noticed later contains object named Test which is basically an array of objects ( object[] ).
As you can see from the json string :
{
"Test": [{
"FirstBool" : 1,
"aString" : "hello"
}]
}
[ means that json object begins an array type and ] means that json object ended an array type.
{ means that json object begins an object type and } means that json object ended an object type.
Which in your case will require to make kind of a custom deserializer using existing methods from Newtonsoft.Json library.
Example for the Test object could be :
JObject obj = JObject.Parse(jsonString);
// now your obj contains field named "Test" that is of type object[]
// to retrieve informations you have to select "Test" token
JToken testToken = obj.SelectToken("Test");
// your token contains now something like " [{ "FirstBool" : 1, "aString" : "hello" }]"
// which basically is an array
// meaning that you have to iterate through this
foreach(var child in token.Children())
{
// and convert it to a Test object
Test test = JsonConvert.DeserializeObject<Test>(child.ToString());
// test now is fully deserialized object
}
Deserialize it as a list:
JsonConvert.DeserializeObject<List<Test>>(json);
...instead of a wrapper object.

Error when deserializing JSON to Object

I need to convert JSON data that I get from a REST API and convert them to CSV for some analytic. The problem is that the JSON data do not necessarily follow the same content, so I can't define a type for mapping. This has become a challenge that is taking too much of my time. I have already created some code, but of course it is not working as it throws exception on this line
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
The error is:
Additional information: Cannot deserialize the current JSON object
(e.g. {"name":"value"}) into type
'System.Collections.Generic.List`1[System.Object]' because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'data', line 2, position 10.
please let me know what I can do to get this going.
A sample of data would be like this, the fields of data can change very often, for example a new field can be added the next day, so I don't have the liberty to create a .Net class to map the data.
{
"data": [
{
"ID": "5367ab140026875f70677ab277501bfa",
"name": "Happiness Initiatives - Flow of Communication/Process & Efficiency",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2014-08-22T17:00:00:000-0400",
"plannedStartDate": "2014-05-05T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2014-12-05T08:10:21:555-0500",
"status": "CPL"
},
{
"ID": "555f452900c8b845238716dd033cf71b",
"name": "UX Personalization Think Tank and Product Strategy",
"objCode": "PROJ",
"percentComplete": 0.0,
"plannedCompletionDate": "2015-12-01T09:00:00:000-0500",
"plannedStartDate": "2015-05-22T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2016-01-04T09:00:00:000-0500",
"status": "APR"
},
{
"ID": "528b92020051ab208aef09a4740b1fe9",
"name": "SCL Health System - full Sitecore implementation (Task groups with SOW totals in Planned hours - do not bill time here)",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2016-04-08T17:00:00:000-0400",
"plannedStartDate": "2013-11-04T09:00:00:000-0500",
"priority": 1,
"projectedCompletionDate": "2013-12-12T22:30:00:000-0500",
"status": "CPL"
}
]
}
namespace BusinessLogic
{
public class JsonToCsv
{
public string ToCsv(string jsonData, string datasetName)
{
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
DataTable table = ToDataTable(data);
StringBuilder result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(table.Columns[i].ColumnName);
result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}
foreach (DataRow row in table.Rows)
{
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(row[i].ToString());
result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}
}
return result.ToString().TrimEnd(new char[] {'\r', '\n'});
}
private DataTable ToDataTable<T>( IList<T> data )
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0 ; i < props.Count ; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0 ; i < values.Length ; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}
}
}
The real issue here is that you are trying to deserialize into a List<object> but your JSON actually represents a single object containing a data property which then contains a list of objects. That is why you are getting this error. Json.Net can't deserialize a single object into a list. I think what you really want to do is define a container class like this:
class Root
{
public List<Dictionary<string, object>> Data { get; set;}
}
Then deserialize like this:
var data = JsonConvert.DeserializeObject<Root>(jsonData).Data;
You will then end up with a list of dictionaries, where each dictionary represents one item in the JSON array. The dictionary key-value pairs are the dynamic values in each item. You can then work with these as you would with any other dictionary. For example, here is how you would dump out all the data:
foreach (var dict in data)
{
foreach (var kvp in dict)
{
Console.WriteLine(kvp.Key + ": " + kvp.Value);
}
Console.WriteLine();
}
Fiddle: https://dotnetfiddle.net/6UaKhJ
What you're looking for is the dynamic type. Though unrelated, this answer contains much of the information on how you'll be able to iterate through the changing properties on your object.
You will need to add some additional work to figure out how to handle your result when it is an array versus a single object as your error shows us. However, this is a good first step for you.
Basically, a dynamic object is a Dictionary, much like how a JSON object is treated in JavaScript. You just need to iterate through each of the KeyValuePair objects within the main object and go through their properties.
var data = JsonConvert.DeserializeObject<dynamic>(jsonData);
var rows = new List<string>();
// Go through the overall object, and get each item in
// the array, or property in a single object.
foreach (KeyValuePair<string, object> item in data)
{
dynamic obj = item.Value;
var row = "";
// Perhaps add a check here to see if there are more
// properties (if it is an item in an array). If not
// then you are working with a single object, and each
// item is a property itself.
foreach (KeyValuePair<string, object> prop in obj)
{
// Very dummy way to demo adding to a CSV
string += prop.Value.ToString() + ",";
}
rows.Add(string);
}
This is far from a complete example, but we don't have enough information to go on to help you finish what you're trying to do.
You are trying to deserialize into a List but your JSON actually represents a single object containing a data property containing list of objects. That is why you are getting this error. Json.Net can't deserialize a single object into a list.
Please try this:Create a class which contain single property on datatype Object and pass this class for deserialization.
class Parent
{
public object Data { get; set;}
}
Then deserialize like this:
var output = JsonConvert.DeserializeObject<Parent>(jsonData);
Try using this class instead of Object
public class Datum
{
public string ID { get; set; }
public string name { get; set; }
public string objCode { get; set; }
public double percentComplete { get; set; }
public string plannedCompletionDate { get; set; }
public string plannedStartDate { get; set; }
public int priority { get; set; }
public string projectedCompletionDate { get; set; }
public string status { get; set; }
}
public class RootObject
{
public List<Datum> data { get; set; }
}
Change to this:
var data = JsonConvert.DeserializeObject<RootObject>(jsonData);
If your data is dynamic so try a dynamic list:
using System.Web.Script.Serialization;
JavaScriptSerializer jss = new JavaScriptSerializer();
var d=jss.Deserialize<dynamic>(str);
Since you're trying to deserialize an object type into a list type, it won't deseralize directly.
You can do this:
var data = JsonConvert.DeserializeObject<ObjectDataList>(jsonData);
var rows = new List<DeserializedData>();
foreach (dynamic item in data)
{
var newData = new DeserializedData();
foreach (dynamic prop in item)
{
var row = new KeyValuePair<string, string>
(prop.Name.ToString(), prop.Value.ToString());
newData.Add(row);
}
rows.Add(newData);
}
Here are new classes
//class for key value type data
class DeserializedData
{
List<KeyValuePair<string, string>> NewData =
new List<KeyValuePair<string, string>>();
internal void Add(KeyValuePair<string, string> row)
{
NewData.Add(row);
}
}
[DataContract]
class ObjectDataList
{
[DataMember(Name ="data")]
List<object> Data { get; set; }
public IEnumerator<object> GetEnumerator()
{
foreach (var d in Data)
{
yield return d;
}
}
}
As far as I can tell, more recent versions of Newtonsoft can actually do this now, no additional work required.
I was working with the binary version however, and this did still have the issue - I had a test where you could configure to use binary or json, and the json version worked just fine, but the binary complained about not getting an array type.
I started using the BsonDataReader for this, and was looking through the properties and methods on it to see how I could best look at the contents, and lo and behold, this had a property called:
reader.ReadRootValueAsArray
Setting this to 'true' did the trick.

json save object to list

Can anyone tell me how to fix this?
class Movie
{
public string Name { get; set; }
public int Year { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Movie> _data = new List<Movie>();
_data.Add(JsonConvert.DeserializeObject<Movie>(File.ReadAllText(#"mov.txt")));
Console.ReadKey();
}}
[{"Name" : "John","Year" : "1990"},
{"Name" : "Mark","Year" : "2005"}]
Theres Exception:
Additional information: Cannot deserialize the current JSON array
(e.g. [1,2,3]) into type 'testjson.Movie' because the type requires a
JSON object (e.g. {"name":"value"}) to deserialize correctly.
Thank you.
It's a JSON array, so you need to deserialize as such:
JsonConvert.DeserializeObject<Movie[]>
So you can call AddRange(), which accepts an IEnumerable<Movie>:
_data.AddRange(JsonConvert.DeserializeObject<Movie[]>(File.ReadAllText(#"mov.txt")));
But in this case you can deserialize the list itself:
List<Movie> _data;
_data = JsonConvert.DeserializeObject<List<Movie>>(File.ReadAllText(#"mov.txt")));
try this (thanks to CodeCaster):
_data.AddRange(JsonConvert.DeserializeObject<List<Movie>>(File.ReadAllText(#"mov.txt")));
because this
[
{ ... },
{ ... },
...
]
in your json means that it is an array. So you have to deserialize it as an array.

Deserializing an anonymous top-level array in subclasses

I've got a JSON response that is contained in an outer array like this:
[
{
"type": "a"
},
{
"type": "b",
"id": 1
},
{
"type": "c",
"name": "somename"
}
]
I've tried to convert this to object like this:
class LoginOptions
{
public IList<ILoginOption> Options { get; set; }
}
interface ILoginOption
{
[JsonProperty("type")]
LoginType LoginType { get; }
}
class LoginOptionA : ILoginOption{
public LoginType LoginType
{
get { return LoginType.A; }
}
}
class LoginOptionB : ILoginOption{
public LoginType LoginType
{
get { return LoginType.B; }
}
[JsonProperty("id")]
public int Id { get; set; }
}
class LoginOptionC : ILoginOption{
public LoginType LoginType
{
get { return LoginType.C; }
}
[JsonProperty("name")]
public string Name { get; set; }
}
Which results in this exception:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Library.Models.Domain.LoginOptions' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
I would rather not implement a collection in my LoginOptions class since that would be way too much overhead when I should be able to just store it in the field. Using the [JsonArray] attribute returns a Cannot create and populate list type Library.Models.Domain.LoginOptions.
Most resources I've found deal with a {"name":"value"} pair for the top array, not an anonymous array. How should I deserialize this?
I've changed my code accordingly:
public sealed class LoginOptions
{
[JsonConverter(typeof(LoginOptionConverter))]
public IList<ILoginOption> Options { get; set; }
}
Where my call dispatcher parses the JSON as such:
private List<ILoginOption> DeserializeObject<List<ILoginOption>>(Stream stream)
{
using (StreamReader sr = new StreamReader(stream))
using (JsonReader reader = new JsonTextReader(sr))
{
return new JsonSerializer().Deserialize<List<ILoginOption>>(reader);
}
}
And a custom converter like this:
internal class LoginOptionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ILoginOption);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var item = JObject.Load(reader);
var data = item["type"].Value<string>();
if (data == "UsernamePassword")
{
return item.ToObject<LoginOptionA>();
}
if (data == "Saml")
{
return item.ToObject<LoginOptionB>();
}
if (data == "Crm")
{
return item.ToObject<LoginOptionC>();
}
throw new ArgumentException("Invalid JSON response");
}
}
This throws the error
Could not create an instance of type Library.Models.Domain.ILoginOption. Type is an interface or abstract class and cannot be instantiated.
Using
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
as advised here did not make a difference.
Note that this error is thrown before ever making it inside the custom converter: when the JsonSerializer is created it throws this error.
Instantiation happens here:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
using (StreamReader sr = new StreamReader(stream))
using (JsonReader reader = new JsonTextReader(sr))
{
return JsonSerializer.Create(settings).Deserialize<List<ILoginOption>>(reader);
}
Since the top level in the JSON is an array, you should deserialize directly to a list. There is no need for a wrapper class to hold the list.
List<ILoginOption> loginOptions =
JsonConvert.DeserializeObject<List<ILoginOption>>(json);
Now, because your list will hold several different types of ILoginObjects, Json.Net will not know which ones to create as it deserializes the list. For that you will need a JsonConverter. This answer shows how you can create a converter to handle this.

Json.NET serializing/ deserializing nested dictionaries

I have been doing some research on this subject, and other than changing my Dictionary to a Collection of KeyValuePair objects, I have not been able to find any solution for the following case.
I have a very simple model called Client, which has a dictionary as one of it's properties and finally I am trying to serialize and deserialize a Dictionary model.
Here is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Automation
{
public class Client
{
public Client(IList<String> environments)
{
this.Environments = new Dictionary<String, Boolean>();
if (environments != null)
foreach (String environment in environments)
this.Environments[environment] = false;
}
public Dictionary<String, Boolean> Environments { get; set; }
}
public static class TestClass
{
public static void Serializer()
{
Dictionary<String, Client> newDict = new Dictionary<String, Client>();
newDict[Guid.NewGuid().ToString()] = new Client(new List<String>() { "A", "B" });
newDict[Guid.NewGuid().ToString()] = new Client(new List<String>() { "A", "B" });
newDict[Guid.NewGuid().ToString()] = new Client(new List<String>() { "A", "B" });
string json = JsonConvert.SerializeObject(newDict, Formatting.Indented);
try
{
Dictionary<String, Client> obj = JsonConvert.DeserializeObject<Dictionary<String, Client>>(json);
}
catch
{
}
}
}
}
this is the json that gets generated:
{
"246af598-89b3-4198-b2cb-9f2d8fe6e03e": {
"Environments": {
"A": false,
"B": false
}
},
"fc43bb8d-65d3-4a97-86f3-cb67567845bd": {
"Environments": {
"A": false,
"B": false
}
},
"90c0158a-69e2-42b0-9072-e695c94a2f7a": {
"Environments": {
"A": false,
"B": false
}
}
}
and here is the exception I am getting when trying to deserialize:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.IList`1[System.String]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path '246af598-89b3-4198-b2cb-9f2d8fe6e03e.Environments.A', line 4, position 11.
Any thoughts, eventually I will be sending this object serialized over the wire via a SignalR request.
Thank you
Just introduce a default constructor for the de-serializer to work with:
public class Client
{
public Client()
{ }
// your other constructor ...
}

Categories