I just downloaded a huge JSON file with all current MTG sets/cards, and am looking to deserialize it all.
I've gotten most of each set deserialized, but I'm running into a snag when trying to deserialize the booster object within each Set:
As you can see from the above two pictures, in each booster object there is a list of strings, but for some booster objects there is also an additional array of more strings. Deserializing an array of exclusively strings isn't a problem. My issue arises when I run into those instances where there is an array of strings within the booster object that need deserializing.
Currently the property I have set up to handle this deserialization is:
public IEnumerable<string> booster { get; set; }
But when I run into those cases where there's another array within booster I get an exception thrown, where Newtonsoft.Json complains it doesn't know how to handle the deserialization.
So, my question then becomes: how can I go about deserializing an array of strings contained within an array of strings? And what would an object need to look like in C# code to handle that sort of deserialization?
You could deserialize the per item as string[] even thought the item wouldn't be a collection. So, provide a custom serializer;
public class StringArrayConverter : 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)
{
JArray array = JArray.Load(reader);
for (int i = 0; i < array.Count; i++)
{
//If item is just a string, convert it string collection
if (array[i].Type == JTokenType.String)
{
array[i] = JToken.FromObject(new List<string> {array[i].ToObject<string>()});
}
}
return array.ToObject<List<string[]>>();
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<string[]>));
}
}
public class JsonObject
{
[JsonConverter(typeof(StringArrayConverter))]
public List<string[]> booster { get; set; }
}
Then deserialize the json;
var data = JsonConvert.DeserializeObject<JsonObject>(json);
Finally, you can deserialize a json like I provided below;
{
"booster": [
"1",
"2",
["3","4"]
]
}
If you are using C# as your programming language then use the below link to generate C# class from JSON string
http://json2csharp.com/
You can then use the generated class in your code to deserialize your json string to object using JsonConvert.DeserializeObject(jssonstring)
The easiest solution would be to change the type to IEnumerable<object>. So it can store string or string[].
Or you could create a class Item with two properties of types string and string[]. Then you could create another property that returns the one that's not null, so now instead of the whole item being an object, you can have a special class that only returns one of two types so you can be sure that you get either a string or a string[]. Hope that makes sense.
Your property: public IEnumerable<Item> booster { get; set; }
public class Item
{
private string _text;
private string[] _array;
public object Value => (object)_text ?? (object)_array;
public Item(string text) { _text = text; }
public Item(string[] array) { _array = array; }
}
When you need to use this value, you can check if it's string or string[] like this:
if(myItem is string text)
{
// operate on variable text
}
else // you can be sure that myItem is of type string[] because we covered this up in the Item class
{
var array = (string[])myItem;
// operate on variable array
}
Another option would be to model "booster" as IEnumerable<string[]> and then use a custom JsonConverter to force strings to arrays. In the process of testing this theory, I wrote a (minimally tested, but functional) converter for you :)
public class ForceStringToArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IEnumerable<string[]>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var listObject = new List<string[]>();
var jObject = JToken.Load(reader);
foreach (JToken token in jObject.Children())
{
if (token.Type == JTokenType.Array)
{
var arrayObj = (JArray)token;
listObject.Add(arrayObj.ToObject<string[]>());
}
else if (token.Type == JTokenType.String)
{
listObject.Add(new string[] { token.ToString() });
}
}
return listObject;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then when you invoke DeserializeObject, pass it your custom converter:
var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<YourTypeHere>(testJson, new ForceStringToArrayConverter() );
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 need to produce a JSON document, that will be parsed by a SSI mechanism on a device. The document will actually be a json serialized dictionary. For the sake of simplicity, let's say, that it should look like this:
var x = new Dictionary<string,object>
{
["A"]=new {x = "<!-- ?A.x -->"},
["B"]=new {x = "<!-- ?B.x -->"}
};
JsonConvert.SerializeObject(x).Dump();
Which produces in LinqPad:
{"A":{"x":"<!-- ?A.x -->"},"B":{"x":"<!-- ?B.x -->"}}
But actually those "x" fields are numbers, and when fetched from the device, they will contain numbers. So I would need to serialize this dictionary without quotes around a field value that is string on C# side:
{"A":{"x":<!-- ?A.x -->},"B":{"x":<!-- ?B.x -->}}
How can I force Newtonsoft Json.NET serializer not to add quotes to the value of specific fields (not all) during serialization?
Thank you.
One way to do it is by introducing new JsonConverter (sample). To separate the functionality of "raw serialization", you could introduce new type that would just wrap a string value, eg.
public class RawJson
{
public string Value { get; private set; }
public RawJson(string value)
{
Value = value;
}
}
Then you just check for this type in converter's CanConvert() and in WriteJson() you can just write
writer.WriteRawValue(((RawJson)value).Value);
And below is the actual solution, based on #kiziu's suggestion to use custom converter. But without custom type. As the converter can be added with the attribute to members too, and not only to classes or the converter itself, I can use it on the property I need. The above LinqPad scratch updated:
internal class RawJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue((string)value);
}
}
class myClass
{
[JsonConverter(typeof(RawJsonConverter))]
public string x;
}
void Main()
{
var x = new Dictionary<string,object>
{
["A"]=new myClass {x = "<!-- ?A.x -->"},
["B"]=new myClass {x = "<!-- ?B.x -->"}
};
JsonConvert.SerializeObject(x).Dump();
}
And the result is, as expected:
{"A":{"x":<!-- ?A.x -->},"B":{"x":<!-- ?B.x -->}}
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) };
}
}
}