Nested JObjects getting serialized as empty arrays - c#

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.

Related

Using Newtonsoft.JSON custom converters to read json with different input

I am using NewtonSoft.Json to read/write our data as json. One (very simplified) example of this is:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"AirportName": "KLAX",
"AirportRunway": "25L"
}
With a C# DTO class that mimicks the properties. Note that we use TypeNameHandling.
We want to change our C# class to a more complex setup:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class LandingEvent
{
public DateTime TimeOfLanding { get; set; }
public Airport Airport { get; set; }
}
which will result in, that new data will be written to JSON as:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"Airport": {
"Name": "KLAX",
"Runway": "25L"
}
}
But we still need to be able to read the old JSON data and parse into the new class structure. And this is what I currently struggle with.
I know that the way to go is probably a specialized JsonConverter. I have a couple of questions in this regard:
How do I read the $type property and instantiate the right type? (my overriden CanConvert() method is fed the name of a base-class (due to the real context being more complex than this example).
I only want to do custom read, if the property AirportName exsist. How do I fall-back to default deserialization, if this is not the case?
Edit: Some clarification is in order. If I create a custom JsonConverter, then CanConvert will receive the type EventBase, but the $type can actually contain either "MyNamespace.LandingEvent, MyAssembly" or "MyNamespace.TakeoffEvent, MyAssembly". Therefore I will probably need to instantiate the returned object myself based on this value. I am not sure how, though.
You can use a custom JsonConverter to do double duty in handling both the polymorphic event types and the varying JSON formats. Below is an example. It works by loading the data into a JObject, where it can read the $type property and instantiate the correct event type. From there, it will try to populate the event object from the JSON. If the Airport fails to deserialize, it will then attempt to read the legacy airport proprties and populate a new Airport instance from that.
class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseEvent).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string type = (string)obj["$type"];
BaseEvent baseEvent;
if (type.Contains(nameof(TakeoffEvent)))
{
baseEvent = new TakeoffEvent();
}
else
{
baseEvent = new LandingEvent();
}
serializer.Populate(obj.CreateReader(), baseEvent);
if (baseEvent.Airport == null)
{
baseEvent.Airport = new Airport
{
Name = (string)obj["AirportName"],
Runway = (string)obj["AirportRunway"]
};
}
return baseEvent;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Note: this assumes your class structure actually looks like this:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class BaseEvent
{
public Airport Airport { get; set; }
}
class TakeoffEvent : BaseEvent
{
public DateTime TimeOfTakeoff { get; set; }
}
class LandingEvent : BaseEvent
{
public DateTime TimeOfLanding { get; set; }
}
To use the converter, add it to the Converters collection in the JsonSerializerSettings, and pass the settings to DeserializeObject():
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = new List<JsonConverter> { new EventConverter() }
};
var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);
Here is a working demo: https://dotnetfiddle.net/jSaq4T
See also: Adding backward compatibility support for an older JSON structure
Classes change, this kind of Json strings change and will get extra features in future versions. You'll keep adjusting your declarations. With Newtonsoft, you can add custom handlers for varying class inheritance and keep using deserialize, but you'll have to maintain that code.
For dynamic Json, I find it easier to use JObject, JArray and JToken instead, to freely parse a Json string. Especially if you're only interested in some of the fields.
I can only give you an example, I think this is (a little) related to your project, but not the same part (smiley)
I use below code to decode part of a glTF 3d-object file produced by Blender in MSFS-converted format. This Json-like format consists of sections. Each Json section looks something like this,
"asset" : {
"extensions" : {
"ASOBO_normal_map_convention" : {
"tangent_space_convention" : "DirectX"
}
},
"generator" : "Extended Khronos glTF Blender I/O v1.0.0",
"version" : "2.0"
},
.. but these sections and their fields are mostly optional and in some GLtf's they are not filled in. It is not "serializable or deserializable" to classes.
I declare some
public JObject AssetObject;
.. filling it in from Json string sJson as follows:
dynamic stuff = JObject.Parse(sJson);
var pp = stuff.Children();
Dictionary<string, bool> d = new Dictionary<string, bool>();
foreach (JProperty jo in pp) d[jo.Name] = true; // all sections
string cSection= "asset";
if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
else
{
AssetObject = (JObject)stuff[cSection];
ParseGLBAsset();
}
Notice the use of a dynamic declaration at first, a section will land in JObject via cast. I store the various parts of the section into string properties. The parse itself takes place in ParseGLBAsset(), this function looks as follows:
public void ParseGLBAsset()
{
foreach (JProperty jbp in AssetObject.Children())
if (jbp.Name == "generator")
{ GLBGenerator = jbp.Value.ToString(); }
else
if (jbp.Name == "extensions")
{
GLBAssetExtensions = jbp.Value.ToString();
LogLine(0, "Asset extensions: " + GLBAssetExtensions);
}
else
if (jbp.Name == "version")
{ GLBVersion = jbp.Value.ToString(); }
LogLine(1, "Found asset.generator=" + GLBGenerator);
LogLine(1, "Found asset.version=" + GLBVersion);
}

Is it possible to deserialize into property rather than an object

Appologies if its already been asked, I could not find anything helpful to my situation.
I need to deserialize a JSON in a property of my object instead of a whole object. The reason I am trying to do it, is that is simply generics.
I have the following situation
For instance I have
Class User
{
int UserId {get;set;}
string Name {get;set;
}
Class Wanted : CustomClass
{
User[] Users {get;set;}
public override void Map(){ }
public override void Scan(){ }
}
My Json is:
[
{
"userId": 1,
"name": "Josh"
},
{
"userId": 5,
"name" : "Martin"
}
]
Is it possible to deserialize(+ generics) my JSON directly into my Wanted class instead of serializing into A and then assign it into Wanted ?
The goal is after the serialization I will have object with type Wanted and an array with 2 users in it.
Since the JSON does not match the class you want to deserialize into, and you cannot change the JSON, you will need to use a custom JsonConverter to bridge the gap.
To make it work you'll need to introduce an interface IHasUsers which your Wanted class (or its base class) will need to implement:
interface IHasUsers
{
User[] Users { get; set; }
}
class Wanted : CustomClass, IHasUsers
{
public User[] Users { get; set; }
...
}
Then you can make a generic converter which will instantiate the Wanted class (or any other class which implements IHasUsers) and populate the Users property:
class UserListConverter<T> : JsonConverter where T: IHasUsers, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(IHasUsers).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
T obj = new T() { Users = array.ToObject<User[]>() };
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then you can deserialize your JSON like this:
Wanted wanted = JsonConvert.DeserializeObject<Wanted>(json, new UserListConverter<Wanted>());
Here is a demo: https://dotnetfiddle.net/KL6Ok6
Hope this is what you were looking for.
Since Wanted is "your desired class", there needs to be an instance of Wanted created somewhere. You might just as well create it yourself rather than having a derserializer do it for you. Once you have done this you can simply set the Users property to the deserialized data:
var wanted = new Wanted() { Users = JsonConvert.DeSerialize<User[]>(myString) };
You don't deserialize some data "into a property" without deserializing it to some object of some type first. Once you have done this you can then set the property to the object that contains the deserialized data.
There is nothing generic about Wanted here though and the deserializer cannot be supposed to figure out that it should create a Wanted or any other type unless you specify the type to derserialize the data to somewhere.
And there is no point of deserializing the data to a type defined at compile time if you don't know that the data matches this type. Then you might as well create an anonymous object or a dictionary of key/value pairs.
You can use Newtonsoft.json . Try below
var files = JArray.Parse(YourJSON);
var recList = files.SelectTokens("$").ToList();
foreach (JObject item in recList.Children())
{
foreach (JProperty prop in item.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
// and add these to an array
}
}

Deserialize array within array

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

How to force the serialization/deserialization of my custom type?

Here is my code:
void Main()
{
var test = new Order()
{
Id = Guid.NewGuid(),
Title = "Test",
Code = new Code("O-123456789") // TODO create a Code.NewCode() later
};
var line = Newtonsoft.Json.JsonConvert.SerializeObject(test).ToString();
Console.WriteLine(line);
}
// Define other methods and classes here
public class Order
{
public Guid Id { get; set; }
public Code Code { get; set; }
public string Title { get; set; }
}
public class Code
{
public string code;
public Code(string code)
{
this.code = code;
}
}
On the console I get this result:
{"Id":"227599fe-c834-4330-84e5-2018abe59e35","Code":{"code":"O-123456789"},"Title":"Test"}
But I want this:
{"Id":"227599fe-c834-4330-84e5-2018abe59e35","Code":"O-123456789","Title":"Test"}
So how can I force my Code type to serialize like I want. Actually, I want the same behavior of Guid(). Or find a way to implement String(). Could you help me on this.
I know I can probably use some attribute to force JSON serialization but I would like something that work for all serialization exactly like the Guid()
You can create a new JsonConverter that deals with your type and then serializes it how you like:
public class CodeSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var code = value as Code;
writer.WriteValue(code.code);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(Code).IsAssignableFrom(objectType);
}
}
Once we have this you can plug it in to your SerializeObject method by setting some properties on JsonSerializerSettings:
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Insert(0, new CodeSerializer());
var line = Newtonsoft.Json.JsonConvert.SerializeObject(test, jsonSerializerSettings).ToString();
Console.WriteLine(line);
// {"Id":"2010e737-a9e8-4b77-bde6-1c50e92c6a30","Code":"O-123456789","Title":"Test"}
Maybe you can do it like this;
public class Order
{
public Guid Id { get; set; }
[JsonIgnore]
public Code Code { get; set; }
public string SerializedCode
{
get
{
if (Code != null)
{
return Code.code;
}
return string.Empty;
}
}
public string Title { get; set; }
}
Output : {"Id":"227599fe-c834-4330-84e5-2018abe59e35","SerializedCode":"O-123456789","Title":"Test"}
Actually, you can't do it for all serialization actions. There is no generic way to perform it. Maybe you can provide own serializer class. But I think, it wouldn't be a good solution. You don't want serialized output and class object
to be different from each other. It can be cause another problems. I suggest you to change your class and properties to perform it.
The answer is the one given in comment by David Watts:
Json.Net converts the .Net Primitive of a Guid to a string (JSON Primitive) under the hood. All details on newtonsoft.com/json/help/html/SerializationGuide.htm
At a high level, the Json.NET serializer will convert primitive .NET values into primitive JSON values, will convert .NET arrays and collections to JSON arrays, and will convert everything else to JSON objects.
For other custom serialization I must use a JsonConverter as explained by Kevin Smith.

JSON.NET Deserialization - Single Result vs Array

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

Categories

Resources