One of the JSON API that I am consuming returns response that varies its data structure depending upon how many results are returned from the query. I am consuming it from C# and using JSON.NET to deserialize the response.
Here is the JSON that is returned from the API
Multiple Result Response:
{
"response": {
"result": {
"Leads": {
"row": [
{
"no": "1",
...
...
...
Single Result Response:
{
"response": {
"result": {
"Leads": {
"row": {
"no": "1",
...
...
...
Note the difference at "row" node which is either is an array in case of multiple results and object in case of single result.
Here are classes that I use to deserialize this data
Classes:
public class ZohoLeadResponseRootJson
{
public ZohoLeadResponseJson Response { get; set; }
}
public class ZohoLeadResponseJson
{
public ZohoLeadResultJson Result { get; set; }
}
public class ZohoLeadResultJson
{
public ZohoDataMultiRowJson Leads { get; set; }
}
public class ZohoDataMultiRowJson
{
public List<ZohoDataRowJson> Row { get; set; }
}
public class ZohoDataRowJson
{
public int No { get; set; }
...
}
The "Multiple Result Response" is deserialized without any problem but when there is only one result in the response, because of the data structure change, the response can't be deserialized. I get an exception
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON
object (e.g. {"name":"value"}) into type
'System.Collections.Generic.List`1[MyNamespace.ZohoDataRowJson]'
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<T>)
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 'response.result.Notes.row.no', line 1, position 44.
Is there a way to handle this in Json.Net with some attribute and hopefully without having to write a converter?
The is inspired by an answer to a similar question.
public class ZohoDataMultiRowJson
{
[JsonConverter(typeof(ArrayOrObjectConverter<ZohoDataRowJson>))]
public List<ZohoDataRowJson> Row { get; set; }
}
public class ArrayOrObjectConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else if (reader.TokenType == JsonToken.StartObject)
{
return new List<T>
{
(T) serializer.Deserialize<T>(reader)
};
}
else
{
throw new NotSupportedException("Unexpected JSON to deserialize");
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
Related
I'm getting a really strange situation where I'm trying to serialize an object returned by a third party API into JSON. I don't have any control over the third party API or the object it returns. The C# POCO I'm trying to serialize looks something like this:
public class JobSummary {
public Job Job { get; set; }
}
public class Job {
public Status Status { get; set; }
}
public class Status {
public object JobOutput { get; set; }
public int Progress { get; set; }
}
Based on what the third party library returns, I would expect it to serialize to the following. At runtime, I can tell that the type of JobOutput is a JObject that contains a single key (Count) and value (0).
{
job: {
status: {
jobOutput: {
Count: 0
},
progress: 100
}
}
}
In this, job and status are obviously objects. progress is an int and jobOutput is a JObject.
If I run any of the following variations:
JToken.FromObject(jobSummary)
JObject.FromObject(jobSummary)
JObject.Parse(jobSummary)
And ToString() or JsonConvert.SerializeObject() the result, I get the following output:
{
job: {
status: {
jobOutput: {
Count: []
},
progress: 100
}
}
}
Notice that Count has become an [].
But if I do jobSummary.Status.JobOutput.ToString(), I correctly get back 0, so I know that the POCO returned by the third party library isn't malformed and has the info I need.
Does anybody know what could be going on? Or how I can correctly serialize the nested JObject?
Edit: I should clarify that I'm on v6.0.8 of Newtonsoft for reasons outside my control, and that the thirdparty assembly that contains the POCO has an unknown version of Newtonsoft ILMerged in it. I don't know if that is relevant.
You wrote that
I should clarify that I'm on v6.0.8 of Newtonsoft for reasons outside my control, and that the thirdparty assembly that contains the POCO has an unknown version of Newtonsoft ILMerged in it.
This explains your problem. The JobOutput contains an object with full name Newtonsoft.Json.Linq.JObject from a completely different Json.NET DLL than the one you are using. When your version of Json.NET tests to see whether the object being serialized is a JToken, it checks objectType.IsSubclassOf(typeof(JToken)) -- which will fail since the ILMerged type is not, in fact, a subclass of your version's type, despite having the same name.
As a workaround, you will need to create custom JsonConverter logic that uses the ToString() methods of the foreign JToken objects to generate output JSON, then writes that JSON to the JSON stream you are generating. The following should do the job:
public class ForeignJsonNetContainerConverter : ForeignJsonNetBaseConverter
{
static readonly string [] Names = new []
{
"Newtonsoft.Json.Linq.JObject",
"Newtonsoft.Json.Linq.JArray",
"Newtonsoft.Json.Linq.JConstructor",
"Newtonsoft.Json.Linq.JRaw",
};
protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var json = value.ToString();
// Fix indentation
using (var stringReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(stringReader))
{
writer.WriteToken(jsonReader);
}
}
}
public class ForeignJsonNetValueConverter : ForeignJsonNetBaseConverter
{
static readonly string [] Names = new []
{
"Newtonsoft.Json.Linq.JValue",
};
protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var underlyingValue = ((dynamic)value).Value;
if (underlyingValue == null)
{
writer.WriteNull();
}
else
{
// JValue.ToString() will be wrong for DateTime objects, we need to serialize them instead.
serializer.Serialize(writer, underlyingValue);
}
}
}
public abstract class ForeignJsonNetBaseConverter : JsonConverter
{
protected abstract IReadOnlyCollection<string> TypeNames { get; }
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive)
return false;
// Do not use the converter for Native JToken types, only non-native types with the same name(s).
if (objectType == typeof(JToken) || objectType.IsSubclassOf(typeof(JToken)))
return false;
var fullname = objectType.FullName;
if (TypeNames.Contains(fullname))
return true;
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then use them in settings as follows:
var settings = new JsonSerializerSettings
{
Converters =
{
new ForeignJsonNetContainerConverter(), new ForeignJsonNetValueConverter()
},
};
var json = JsonConvert.SerializeObject(summary, Formatting.Indented, settings);
Notes:
The converters work by assuming that types whose FullName matches a Json.NET type's name are, in fact, Json.NET types from a different version.
JValue.ToString() returns localized values for DateTime objects (see here for details), so I created a separate converter for JValue.
I also fixed the indentation to match.
Mockup fiddle here.
I am using Newtonsoft to parse some JSon into a .Net type. The json contains an array of arrays called 'data'. I would like to make each array within the data array it's own type, but am unsure how to do this.
Hopefully the code below demonstrates this.
public class TheData
{
[JsonProperty(PropertyName = "data")]
public List<object> dataItems { get; set; }
}
Usage:
string json =
"{\"data\":[[\"20180511\",1094391],[\"20180504\",1097315],[\"20180427\",1100221],[\"20180420\",1094455],[\"20180413\",1093023]]}";
var myObj = JsonConvert.DeserializeObject<TheData>(json);
This works ok, however, I would like to change the type of dataItems from List to List as below:
public class TheData
{
[JsonProperty(PropertyName = "data")]
public List<DataItem> dataItems { get; set; }
}
public class DataItem
{
public string deldate { get; set; }
public int value { get; set; }
}
However, this results in an exception:
Newtonsoft.Json.JsonSerializationException occurred
HResult=-2146233088
Message=Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'CE.FOTools.Feeds2.EIA.DataItem' 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<T> 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 'data[0]', line 1, position 10.
The error message suggests my desired outcome may not be possible, but can anyone suggest how to correct this? I have no control over the JSON format (unles I operate on the string once it is retrieved). I'm using .Net 4.5 if that makes any difference.
I think the least obtrusive way is to use custom converter. For example:
class DataItemConverter : JsonConverter<DataItem> {
public override void WriteJson(JsonWriter writer, DataItem value, JsonSerializer serializer) {
// if item can be null - handle that
writer.WriteStartArray();
writer.WriteValue(value.deldate);
writer.WriteValue(value.value);
writer.WriteEndArray();
}
public override DataItem ReadJson(JsonReader reader, Type objectType, DataItem existingValue, bool hasExistingValue, JsonSerializer serializer) {
var ar = serializer.Deserialize<List<object>>(reader);
// perform some checks for length and data types, omitted here
var result = new DataItem();
result.deldate = (string) ar[0];
result.value = Convert.ToInt32(ar[1]);
return result;
}
}
Then specify that this converted should be used by decorating your type:
[JsonConverter(typeof(DataItemConverter))]
public class DataItem {
public string deldate { get; set; }
public int value { get; set; }
}
And after that it should work as you expect.
If generic JsonConverter<> is not available in your Json.NET version - use non-generic one:
class DataItemConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var item = (DataItem) value;
// if null is possible - handle that
writer.WriteStartArray();
if (item != null) {
writer.WriteValue(item.deldate);
writer.WriteValue(item.value);
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var ar = serializer.Deserialize<List<object>>(reader);
// perform some checks for length and data types, omitted here
var result = new DataItem();
result.deldate = (string) ar[0];
result.value = Convert.ToInt32(ar[1]);
return result;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(DataItem);
}
}
I'm trying to Deserialize data from binance API. The format in the website is:
{
"lastUpdateId": 82930322,
"bids": [
["0.09766700","12.64700000",[]],
["0.09766600","0.19500000",[]],
["0.09765800","0.30300000",[]],
["0.09765600","3.50000000",[]],
["0.09765500","0.14900000",[]]
],
I try to save the data to:
public string NameOfCoin { get; set; }
public string[][] bids { get; set; }
And I get exception that it can't read the [] in the end of the array. I tryed also for a diffrent format like float or string withour array and it dosent work.
Well, the simplest solution is to change the type of your bids property from string[][] to object[][]. That will allow the deserialization to succeed, but working with the bids array will be awkward. You will have to do type checking on the items and cast them appropriately when you use them.
A better idea is to filter out the unwanted empty array values during deserialization. You can do that with a custom JsonConverter (assuming you are using Json.Net -- your question did not indicate what JSON serializer you are using). Here is one that should do the job:
class CustomConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray rows = JArray.Load(reader);
foreach (JArray row in rows.Children<JArray>())
{
foreach (JToken item in row.Children().ToList())
{
if (item.Type != JTokenType.String)
item.Remove();
}
}
return rows.ToObject<string[][]>(serializer);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
To use the converter, mark the bids property in your class with a [JsonConverter] attribute like this:
[JsonConverter(typeof(CustomConverter))]
public string[][] bids { get; set; }
Then you can deserialize to your class as usual and it should "just work".
Working demo: https://dotnetfiddle.net/TajQt4
I am having trouble trying to determine how to make my Serialization Properly be able to access a single result, as well as an array.
When I make a REST call looking for something on a server, sometimes it will return an Array of models, but if the search results only have a single model, it will not be returned as an error. This is when I get an exception that I cannot deserialize because the Object Property is expecting an array, but is instead receiving a single object.
Is there a way to define my class so that it can handle a single object of type ns1.models when that is returned instead of an array of objects?
[JsonObject]
public class Responses
{
[JsonProperty(PropertyName = "ns1.model")]
public List<Model> Model { get; set; }
}
Response that can be deserialized:
{"ns1.model":[
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}},
{"#mh":"0x21a400","ns1.attribute":{"#id":"0x1006e","$":"servername2"}}
]}
Response that cannot be serialized (because JSON includes only a singe "ns1.model"):
{"ns1.model":
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}}
}
Exception:
Newtonsoft.Json.JsonSerializationException was unhandled HResult=-2146233088 Message=Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ConsoleApplication1.Model]' 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<T>) 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 '['ns1.model-response-list'].['ns1.model-responses'].['ns1.model'].#mh', line 1, position 130
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class Response
{
[JsonProperty("ns1.model")]
[JsonConverter(typeof(SafeCollectionConverter))]
public List<Model> Model { get; set; }
}
public class Model
{
[JsonProperty("#mh")]
public string Mh { get; set; }
[JsonProperty("ns1.attribute")]
public ModelAttribute Attribute { get; set; }
}
public class ModelAttribute
{
[JsonProperty("#id")]
public string Id { get; set; }
[JsonProperty("$")]
public string Value { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
I think your question has been answered already. Please have a look at this thread:
How to handle both a single item and an array for the same property using JSON.net .
Basically the way to do it is to define a custom JsonConvertor for your property.
There is not an elegant solution to your problem in the current version of JSON.NET. You will have to write custom parsing code to handle that.
As #boyomarinov said you can develop a custom converter, but since your JSON is pretty simple you can just parse your JSON into an object and then handle the two cases like this:
var obj = JObject.Parse(json);
var responses = new Responses { Model = new List<Model>() };
foreach (var child in obj.Values())
{
if (child is JArray)
{
responses.Model = child.ToObject<List<Model>>();
break;
}
else
responses.Model.Add(child.ToObject<Model>());
}
Use JRaw type proxy property ModelRaw:
public class Responses
{
[JsonIgnore]
public List<Model> Model { get; set; }
[JsonProperty(PropertyName = "ns1.model")]
public JRaw ModelRaw
{
get { return new JRaw(JsonConvert.SerializeObject(Model)); }
set
{
var raw = value.ToString(Formatting.None);
Model = raw.StartsWith("[")
? JsonConvert.DeserializeObject<List<Model>>(raw)
: new List<Model> { JsonConvert.DeserializeObject<Model>(raw) };
}
}
}
My JSON looks like
{
"d": {
"__type": "CubeJsonData",
"SessionID": null,
"Code": 0,
"Message": "",
"Rows": {},
"Columns": {
"Place 1": [
0,
1
],
"Place 2": [
0,
2,
4,
6
],
},
"Set": [
[
"Number 1"
],
[
"Number 2"
],
[
"Number 3"
]
]
}
}
I need to get the following values
List<string> Columns must contain: "Place 1", "Place 2"
List<string> Set must contain: "Number 1", "Number 2", "Number 3"
My caller is
var settings = new JsonSerializerSettings();
settings.Converters.Add(new AssosiativeArrayConverter());
var staffAlerts = JsonConvert.DeserializeObject<List<AlertDetails>>(jo.ToString(), settings);
My JsonConverter is
class AssosiativeArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(string)) || (objectType == typeof(List<string>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
var l = new List<string>();
reader.Read();
while (reader.TokenType != JsonToken.EndArray)
{
l.Add(reader.Value as string);
reader.Read();
}
return l;
}
else
{
return new List<string> { reader.Value as string };
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{//to do
}
}
My class is
class StaffDetail
{
public string __type { get; set; }
public string SessionID { get; set; }
public string Code { get; set; }
public string Message { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Rows { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Columns { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Set { get; set; }
}
I am getting an error
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ReportingServicesAlerts.AlertDetails]' 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.
Can you help me figure out what I'm doing wrong?
You have a lot of problems going on here. Let me start with the reason you are getting this error: your JSON contains a single outer object, but you are trying to deserialize it into a list. This won't work. If there's just a single object in the JSON, you need to deserialize into a single object.
Second issue, the data for your StaffDetail class is not at the top level of your JSON; it is one level down-- in the value of the d property of the outer JSON object. To fix this, you need to introduce a wrapper class and deserialize into that. Then you can retrieve the StaffDetail from the wrapper.
Third issue, it looks like you are trying to flatten the Columns and Set data from the JSON into List<string> properties in your class. It looks like you have correctly realized that you need a converter to do this; however, your converter doesn't handle the JSON data correctly-- it is assuming it is going to get just a simple array of strings or a simple string. Neither Columns nor Set is structured in this way in the JSON, and furthermore, the data is structured differently for both of them. The former is an object containing properties whose values are arrays of integers. The latter is an array of arrays of strings. Since they are different structures, they need to be handled differently. I would suggest using two different converters in this case.
Fourthly, while you correctly decorate your StaffDetail class with [JsonConverter] attributes to indicate which properties should use your converter, you incorrectly also add the converter to the serializer settings. The problem with this is that your converter says in CanConvert that it can handle any string or any list of strings. If you apply the converter in the settings that means that Json.Net will try to use your converter for any property anywhere that is either a string or a list of strings. Clearly, you do not want this-- your converter is really intended just to handle one specific case.
Fifth, it looks like you have also decorated your Rows property with a [JsonConverter] attribute, but the JSON shows an empty object. Will this field have any data that you care about? If not, just declare it as object; if you do care, please show an example of what might be in there. Or if you know that it will be structured the same as either Columns or Set, then you can keep it as List<string> and reuse one of those converters.
There are also some other minor issues in your question such as your JSON being invalid due to an extra comma (already pointed out by #frno), and the fact that your call to JsonConvert.DeserializeObject() mentions a class called AlertDetails but the class you show is actually named StaffDetail. But we'll chalk those up to simple copy-paste mistakes.
Whew!
OK, so how do we fix all this?
Let start with your classes. I mentioned that you need a wrapper class since your data is actually one level down in the JSON; here's what that would look like:
class Wrapper
{
public StaffDetail d { get; set; }
}
For your StaffDetail class, I've changed the Columns and Set properties to use different converters, since the JSON is different for each. I'll define those converters next. I also changed the type of Rows to object and removed the [JsonConverter] attribute for now, since it's not clear from the question how that field should be handled. If the data will be structured like Columns or Set then you can change it back and use the appropriate converter, as I mentioned.
class StaffDetail
{
public string __type { get; set; }
public string SessionID { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public object Rows { get; set; }
[JsonConverter(typeof(ColumnsConverter))]
public List<string> Columns { get; set; }
[JsonConverter(typeof(SetConverter))]
public List<string> Set { get; set; }
}
Here is the converter which will handle the Columns data. This converter will take a JSON object and extract the property names into a list of strings.
class ColumnsConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// this converter can handle converting some JSON to a List<string>
return objectType == typeof(List<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Convert an object to a flat list of strings containing
// just the property names from the object.
JObject obj = JObject.Load(reader);
return obj.Properties().Select(p => p.Name).ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is the converter which will handle the Set data. This converter will take an array of arrays of strings and convert it into a flat list of strings.
class SetConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// this converter can handle converting some JSON to a List<string>
return objectType == typeof(List<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Convert an array of arrays of strings to a flat list of strings
JArray array = JArray.Load(reader);
return array.Children<JArray>()
.SelectMany(ja => ja.Children(), (ja, jt) => jt.Value<string>()).ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To deserialize, you can call JsonConvert.DeserializeObject() like this. Notice how I deserialize into the Wrapper class, then retrieve the StaffDetail from it. Also notice that I don't need to (and shouldn't in this case) pass the converters to the deserializer. They will get picked up automatically and at the appropriate times by virtue of the [JsonConverter] attributes on the StaffDetail class properties.
StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;
Here is simple demo program to show how it all works:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""d"": {
""__type"": ""CubeJsonData"",
""SessionID"": null,
""Code"": 0,
""Message"": """",
""Rows"": {},
""Columns"": {
""Place 1"": [
0,
1
],
""Place 2"": [
0,
2,
4,
6
]
},
""Set"": [
[
""Number 1""
],
[
""Number 2""
],
[
""Number 3""
]
]
}
}";
StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;
Console.WriteLine("Columns: " + string.Join(", ", detail.Columns));
Console.WriteLine("Set: " + string.Join(", ", detail.Set));
}
}
Output:
Columns: Place 1, Place 2
Set: Number 1, Number 2, Number 3
your Json is a bit weird, if you can change it. Nevertheless, correct classes :
(+ DONT FORGET TO REMOVE A COMMA I WROTE ABOUT )
public class Columns
{
[JsonProperty(PropertyName="Place 1")]
public List<int> Place1;
[JsonProperty(PropertyName="Place 2")]
public List<int> Place2;
}
public class Rows { }
public class D
{
public string __type;
public object SessionID;
public int Code;
public string Message;
public Rows Rows;
public Columns Columns;
public List<List<string>> Set;
}
public class StaffDetail
{
public D d { get; set; }
}
and a single simple way to get it all
var result = JsonConvert.DeserializeObject<StaffDetail>(json);
then just get the properties all you want, like
result.d.Columns.Place1[0] // for example