Deserializing JSON when sometimes array and sometimes object - c#

I'm having a bit of trouble deserializing data returned from Facebook using the JSON.NET libraries.
The JSON returned from just a simple wall post looks like:
{
"attachment":{"description":""},
"permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
The JSON returned for a photo looks like:
"attachment":{
"media":[
{
"href":"http://www.facebook.com/photo.php?fbid=12345",
"alt":"",
"type":"photo",
"src":"http://photos-b.ak.fbcdn.net/hphotos-ak-ash1/12345_s.jpg",
"photo":{"aid":"1234","pid":"1234","fbid":"1234","owner":"1234","index":"12","width":"720","height":"482"}}
],
Everything works great and I have no problems. I've now come across a simple wall post from a mobile client with the following JSON, and deserialization now fails with this one single post:
"attachment":
{
"media":{},
"name":"",
"caption":"",
"description":"",
"properties":{},
"icon":"http://www.facebook.com/images/icons/mobile_app.gif",
"fb_object_type":""
},
"permalink":"http://www.facebook.com/1234"
Here is the class I am deserializing as:
public class FacebookAttachment
{
public string Name { get; set; }
public string Description { get; set; }
public string Href { get; set; }
public FacebookPostType Fb_Object_Type { get; set; }
public string Fb_Object_Id { get; set; }
[JsonConverter(typeof(FacebookMediaJsonConverter))]
public List<FacebookMedia> { get; set; }
public string Permalink { get; set; }
}
Without using the FacebookMediaJsonConverter, I get an error: Cannot deserialize JSON object into type 'System.Collections.Generic.List`1[FacebookMedia]'.
which makes sense, since in the JSON, Media is not a collection.
I found this post which describes a similar problem, so I've attempted to go down this route: Deserialize JSON, sometimes value is an array, sometimes "" (blank string)
My converter looks like:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return serializer.Deserialize<List<FacebookMedia>>(reader);
else
return null;
}
Which works fine, except I now get a new exception:
Inside JsonSerializerInternalReader.cs, CreateValueInternal(): Unexpected token while deserializing object: PropertyName
The value of reader.Value is "permalink". I can clearly see in the switch that there's no case for JsonToken.PropertyName.
Is there something I need to do differently in my converter? Thanks for any help.

A very detailed explanation on how to handle this case is available at "Using a Custom JsonConverter to fix bad JSON results".
To summarize, you can extend the default JSON.NET converter doing
Annotate the property with the issue
[JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
public List<OrderItem> items;
Extend the converter to return a list of your desired type even for a single object
public class SingleValueArrayConverter<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)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
} else if (reader.TokenType == JsonToken.StartArray) {
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
As mentioned in the article this extension is not completely general but it works if you are fine with getting a list.

The developer of JSON.NET ended up helping on the projects codeplex site. Here is the solution:
The problem was, when it was a JSON object, I wasn't reading past the attribute. Here is the correct code:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<FacebookMedia>>(reader);
}
else
{
FacebookMedia media = serializer.Deserialize<FacebookMedia>(reader);
return new List<FacebookMedia>(new[] {media});
}
}
James was also kind enough to provide unit tests for the above method.

Based on Camilo Martinez's answer above, this is a more modern, type-safe, leaner and complete approach using the generic version of JsonConverter and C# 8.0 as well as implementing the serialization part. It also throws an exception for tokens other than the two expected according to the question. Code should never do more than required otherwise you run the risk of causing a future bug due to mishandling unexpected data.
internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<ICollection<T>> where T : class, new()
{
public override void WriteJson(JsonWriter writer, ICollection<T> value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value);
}
public override ICollection<T> ReadJson(JsonReader reader, Type objectType, ICollection<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return reader.TokenType switch
{
JsonToken.StartObject => new Collection<T> {serializer.Deserialize<T>(reader)},
JsonToken.StartArray => serializer.Deserialize<ICollection<T>>(reader),
_ => throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.")
};
}
}
And then decorate the property thus:
[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;
I've changed the property type from List<> to ICollection<> as a JSON POCO typically need only be this weaker type, but if List<> is required, then just replaced ICollection and Collection with List in all the above code.

take a look at the System.Runtime.Serialization namespace in the c# framework, it's going to get you to where you want to be very quickly.
If you want, you can check out some example code in this project (not trying to plug my own work but i just finished pretty much exactly what you are doing but with a different source api.
hope it helps.

.Net Framework
using Newtonsoft.Json;
using System.IO;
public Object SingleObjectOrArrayJson(string strJson)
{
if(String.IsNullOrEmpty(strJson))
{
//Example
strJson= #"{
'CPU': 'Intel',
'PSU': '500W',
'Drives': [
'DVD read/writer'
/*(broken)*/,
'500 gigabyte hard drive',
'200 gigabyte hard drive'
]
}";
}
JsonTextReader reader = new JsonTextReader(new StringReader(strJson));
//Initialize Read
reader.Read();
if (reader.TokenType == JsonToken.StartArray)
{
return JsonConvert.DeserializeObject<List<Object>>(strJson);
}
else
{
Object media = JsonConvert.DeserializeObject<Object>(strJson);
return new List<Object>(new[] {media});
}
}
Note:
"Object" must be defined according to the Json attributes of your response

Expounding upon Martinez and mfanto's answer for Newtonsoft. It does work with Newtonsoft:
Here is an example of doing it with an array instead of a list (and correctly named).
public class SingleValueArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject
|| reader.TokenType == JsonToken.String
|| reader.TokenType == JsonToken.Integer)
{
return new T[] { serializer.Deserialize<T>(reader) };
}
return serializer.Deserialize<T[]>(reader);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Then over the attribute write this:
[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }
Newtonsoft will do the rest for you.
return JsonConvert.DeserializeObject<T>(json);
Cheers to Martinez and mfanto!
Believe it or not, this will work with sub items. (It may even have to.) So... inside of my InsuranceInfo, if I have another object/array hybrid, use this again on that property.
This will also allow you to reserialize the object back to json. When it does reserialize, it will always be an array.

I think you should write your class like this...!!!
public class FacebookAttachment
{
[JsonProperty("attachment")]
public Attachment Attachment { get; set; }
[JsonProperty("permalink")]
public string Permalink { get; set; }
}
public class Attachment
{
[JsonProperty("media")]
public Media Media { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("caption")]
public string Caption { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("properties")]
public Properties Properties { get; set; }
[JsonProperty("icon")]
public string Icon { get; set; }
[JsonProperty("fb_object_type")]
public string FbObjectType { get; set; }
}
public class Media
{
}
public class Properties
{
}

Related

JSON.net TypeNameHandling : customize Auto

I am serializing classes using JSON.net and using TypeNameHandling=Auto to insert the "$type" annotation. For the sake of the example, let's say this is my class:
[JsonObject]
public class MyClass
{
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
public ISomeInterfaceB MyB { get; set; }
}
With this, both "MyA" and "MyB" receive the "$type" attribute. Unfortunately, I am in a weird situation where I need "MyA" to have "$type" but "MyB" should not have "$type". Does json.net have anything that would allow me to customize the behaviour of TypeNameHandling.Auto so that I can manually choose where I want it or not?
As for why I would want to do such a thing, the complicated reason is that I am migrating to JSON.net from another legacy serializer, and I am attempting to minimize the differences between the old and the new serializer to avoid having to rewrite large amounts of javascript code consuming the JSON.
You can achieve this using a custom converter.
Say this is your class:
[JsonObject]
public class MyClass {
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
[JsonConverter(typeof(NoTypeConverter))]
public ISomeInterfaceA MyB { get; set; }
}
We need a custom converter:
public class NoTypeConverter: JsonConverter<ISomeInterfaceA> {
public override ISomeInterfaceA ReadJson(JsonReader reader, Type objectType, ISomeInterfaceA existingValue, bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, ISomeInterfaceA value, JsonSerializer serializer)
{
// Note: this is not the most efficient way to do this, but you can customise this how you see fit.
writer.WriteRawValue(JsonConvert.SerializeObject(value));
}
public override bool CanWrite => true;
public override bool CanRead => false;
}
Then:
var stuff = JsonConvert.SerializeObject(new MyClass
{ MyA = new A { Banana = "cheese" },
MyB = new A { Banana = "notype"} },
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
Console.WriteLine(stuff); // {"MyA":{"$type":"whatever, here","Banana":"cheese"},"MyB":{"Banana":"notype"}}
Note, however, that you can not deserialize BACK to your type this way because the type info is missing for MyB
var x = JsonConvert.DeserializeObject<MyClass>(stuff,
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
that will throw
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Could not create an instance of type ISomeInterfaceA. Type is an interface or abstract class and cannot be instantiated. Path 'MyB.Banana', line 1, position 69.

Newtonsoft.Json.JsonConvert.DeserializeObject into Newtonsoft.Json.Linq.JObject issues

I am working on a C# project.
[
{"FirstName":"XYZ","LastName":"SSS"},
{"FirstName":"ABC","LastName":"NNN"}
]
Each row represents an object of class ChildDTO.
I read the above data from the file and am trying to deserialize into a ParentCollection object like below:
string file = System.IO.File.ReadAllText("Children_CA.txt");
ParentCollection pCollection = Newtonsoft.Json.JsonConvert.DeserializeObject<ParentCollection>(file);
My DTO classes are like below:
[Serializable]
public class ParentCollection : CollectionBase
{
public void Add(ChildDTO dto)
{
//List is from Systems.Collections.CollectionBase class
List.Add(dto);
}
}
[Serializable]
public class ChildDTO
{
// properties like FirstName and LastName goes here
}
I cannot change my DTO classes since they are old and already in production from the past 20 years and many applications are using them.
When I see Quick Watch on pCollection, I notice the collection is having objects of type Newtonsoft.Json.Linq.JObject. I am hoping to have objects of type ChildDTO
Please let me know what mistake I am doing.
The problem is that CollectionBase, and by extension your ParentCollection class, are not generic, so Json.Net doesn't know that the items in the collection are supposed to be ChildDTO objects. Since it doesn't know what type the items are, it just uses JObject instead. You can fix the problem using a custom JsonConverter class like this:
class ParentCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParentCollection);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ParentCollection pc = new ParentCollection();
JArray array = JArray.Load(reader);
foreach (var item in array)
{
pc.Add(item.ToObject<ChildDTO>());
}
return pc;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, pass it to DeserializeObject<T> like this:
ParentCollection pCollection =
JsonConvert.DeserializeObject<ParentCollection>(json, new ParentCollectionConverter());
Working demo here: https://dotnetfiddle.net/3GM22c
Good morning. If you need the ChildDto's list, please convert it to List. Please refer to the code and image below. Many thanks.
string file = System.IO.File.ReadAllText("Children_CA.txt");
var results1 = JsonConvert.DeserializeObject<List<ChildDTO>>(file);

Deserialize JSON object as .NET HashSet

Here's an example of raw JSON data:
{ "Standards": { "1": "1" } }
I want to deserialize the data to:
public class Model
{
public HashSet<String> Standards { get; set; }
}
The Standards field actually has the Dictionary<String, String> type. Somehow the keys and values are always equal. As the types are incompatible, I'm looking for a way to perform a custom deserialization of this field.
A solution based on JSON.NET library is preferred.
P.S.: I have no control over the data serialization process.
You can handle this with a custom JsonConverter. Here is the code you would need:
public class CustomHashSetConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HashSet<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new HashSet<string>(jo.Properties().Select(p => p.Name));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
HashSet<string> hashSet = (HashSet<string>)value;
JObject jo = new JObject(hashSet.Select(s => new JProperty(s, s)));
jo.WriteTo(writer);
}
}
To use the converter, add a [JsonConverter] attribute to your model like this:
public class Model
{
[JsonConverter(typeof(CustomHashSetConverter))]
public HashSet<string> Standards { get; set; }
}
Then, just deserialize as normal and it should work:
Model model = JsonConvert.DeserializeObject<Model>(json);
Here is a round-trip demo: https://dotnetfiddle.net/tvHt5Y

Declared properties won't be serialized if its declaring type is a dynamic object

Let's say I've the following dynamic object:
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
}
If I serialize it using JsonConvert.SerializeObject(new SomeDynamicObject { Text = "hello world" }) it'll return {} instead of { "Text": "hello world" }.
I suspect the issue is that JSON.NET thinks it's a full dynamic object while my case is a dynamic object with declared members.
Is there any serialization settings or built-in converter that could be configured so JSON.NET can serialize both kinds of members?
To avoid confusion
Actual use case: I don't know which will be the types being serialized but I need to cover the whole use case of serializing declared properties of a dynamic object.
That is, I can't use attributes. That's why I'm asking if there's some converter or a serialization setting that can generalize this use case.
Update for non-attribute converter
Since you can't decorate, you lose a lot of power. Once the JsonWriter has converted to a JObject, the dynamic properties appear to be lost.
However, you can always use a little reflection in a custom converter's WriteJson method to serialize non-dynamic types.
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
public DynamicObject DynamicProperty { get; set; }
}
public class CustomDynamicConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties().Where(x => x.PropertyType != typeof(DynamicObject)).ToList();
JObject o = (JObject)JToken.FromObject(value);
properties.ForEach(x =>
{
o.AddFirst(new JProperty(x.Name, x.GetValue(value)));
});
o.WriteTo(writer);
}
}
If you explicitly decorate your properties with [JsonProperty], the serializer will pick them up, even if the containing type is dynamic.
public class SomeDynamicObject : DynamicObject
{
[JsonProperty]
public string Text { get; set; }
}
when serialized correctly outputs:
{"Text":"hello world"}

Proper way of using Newtonsoft Json ItemConverterType

I have some bad data coming back from a web service which I cannot change. The service returns a JSON list of customers. Inside this list, each customer also has a list of jobs. But the JSON coming back is a string for the jobs.
So: Jobs: "[]" Instead of Jobs: []
So I defined the class as
[JsonProperty(PropertyName = "JOBS", ItemConverterType = typeof(StringToJobsConverter))]
public List<JobClass> Jobs { get; set; }
I created the class, and created the conversion method inside it as follows:
return JsonConvert.DeserializeObject<List<JobClass>>(existingValue.ToString());
No luck. The error retuned is Could not cast or convert from System.String to System.Collections.Generic.List`1[AppNamespace.JobClass].
Breakpoints in the converter code are never hit. Can anyone see what I'm doing wrong?
UPDATE
I found the issue but don't know how to fix. The converter is being applied to the JobClass inside the list. Not to the List itself. I want the converter applied one time only to the List deserialization. Instead it's applied to each JobClass record inside the list.
string json = #"{str1:""abc"",list:""[1,2,3]"", str2:""def""}";
var temp = JsonConvert.DeserializeObject<Temp>(json);
public class Temp
{
public string str1;
[JsonConverter(typeof(StringConverter<List<int>>))]
public List<int> list;
public string str2;
}
public class StringConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<T>((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Categories