c# Newtonsoft.json DeserializeObject Issue - json.net

Hopefully someone can help me with my issue.
I'm working with a 3rd party API that, depending on the configuration of the product in the backend, will return a different structure of JSON as the response.
I've included JSON samples showing the different outcomes depending on the product. There are 3 scenarios for the FreeGifts section of the product:
Has 2 or more free gifts.
Has 1 free gifts.
Has no free gifts
Scenario 1
{
"FreeGifts": [{
"FreeGift": [{
"SKU": "BOWS-SMALL-ALFIE"
},
{
"SKU": "BOWS-LARGE-ALONZO"
},
{
"SKU": "BOWS-LARGE-CLANCY"
},
{
"SKU": "BOWS-SMALL-ALVIN"
},
{
"SKU": "BOWS-SMALL-CLARK"
}
]
}]
}
Scenario 2
{
"FreeGifts": [{
"FreeGift": {
"SKU": "BOWS-SMALL-ALVIN"
}
}]
}
Scenario 3
{
"FreeGifts": [
""
]
}
Sites like http://json2csharp.com/ and https://jsonutils.com/ provide me with 3 different class definitions depending on the scenario.
If I had only 1 of these in the structure I could probably deal with it but I have around 7 or 8. It's impossible for me to cater for it.
I'm completely stumped as to how I get Newtonsoft.json to work with the ambiguity that the API produces.
Do I need to go back to the provider and ask them if they can change it?!?

You can use SingleOrArrayConverter<FreeGift> from this answer to How to handle both a single item and an array for the same property using JSON.net by Brian Rogers along with TolerantObjectCollectionConverter<FreeGifts> from this answer to How can I ignore a blank array inside an array of JSON objects while deserializing? to successfully deserialize all 3 JSON variants. To do so, define your model and apply the appropriate JSON converter as follows:
public class Root
{
[JsonConverter(typeof(TolerantObjectCollectionConverter<FreeGifts>))]
public List<FreeGifts> FreeGifts { get; set; }
}
public class FreeGifts
{
[JsonConverter(typeof(SingleOrArrayConverter<FreeGift>))]
public List<FreeGift> FreeGift { get; set; }
}
public class FreeGift
{
public string SKU { get; set; }
}
class SingleOrArrayConverter<T> : JsonConverter
{
// Taken from the answer to
// https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// https://stackoverflow.com/a/18997172
// by Brian Rogers
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
return null;
var list = existingValue as List<T> ?? new List<T>();
if (tokenType == JsonToken.StartArray)
{
serializer.Populate(reader, list);
}
else
{
list.Add(serializer.Deserialize<T>(reader));
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class TolerantObjectCollectionConverter<TItem> : JsonConverter
{
// Taken from the answer to
// https://stackoverflow.com/questions/49030516/how-can-i-ignore-a-blank-array-inside-an-array-of-json-objects-while-deserializi
// https://stackoverflow.com/a/49078620/
public override bool CanConvert(Type objectType)
{
return !objectType.IsArray && objectType != typeof(string) && typeof(ICollection<TItem>).IsAssignableFrom(objectType);
}
public override bool CanWrite { get { return false; } }
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)
{
// Get contract information
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonArrayContract;
if (contract == null || contract.IsMultidimensionalArray || objectType.IsArray)
throw new JsonSerializationException(string.Format("Invalid array contract for {0}", objectType));
// Process the first token
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
return null;
if (tokenType != JsonToken.StartArray)
throw new JsonSerializationException(string.Format("Expected {0}, encountered {1} at path {2}", JsonToken.StartArray, reader.TokenType, reader.Path));
// Allocate the collection
var collection = existingValue as ICollection<TItem> ?? (ICollection<TItem>)contract.DefaultCreator();
// Process the collection items
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.EndArray:
return collection;
case JsonToken.StartObject:
case JsonToken.Null:
collection.Add(serializer.Deserialize<TItem>(reader));
break;
default:
reader.Skip();
break;
}
}
// Should not come here.
throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
Notes:
[JsonConverter(typeof(TolerantObjectCollectionConverter<FreeGifts>))] handles the fact that the upper-level "FreeGifts": [] array may sometimes contain an unwanted string value. The value is simply skipped.
[JsonConverter(typeof(SingleOrArrayConverter<FreeGift>))] handles the fact that the "FreeGift" property value may sometimes be either a single object or an array of objects.
You could combine the two converters in situations where a property value might be an array with invalid items or a single object not contained in an array. However, this isn't the case in the three JSON examples shown.
Sample working .Net fiddle.

Related

How to use the NewtonSoft JsonConverterAttribute and JToken.FromObject() without calling the JsonConverter recursively [duplicate]

I wrote this simple code to Serialize classes as flatten, but when I use [JsonConverter(typeof(FJson))] annotation, it throws a StackOverflowException. If I call the SerializeObject manually, it works fine.
How can I use JsonConvert in Annotation mode:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.id = 1;
a.b.name = "value";
string json = null;
// json = JsonConvert.SerializeObject(a, new FJson()); without [JsonConverter(typeof(FJson))] annotation workd fine
// json = JsonConvert.SerializeObject(a); StackOverflowException
Console.WriteLine(json);
Console.ReadLine();
}
}
//[JsonConverter(typeof(FJson))] StackOverflowException
public class A
{
public A()
{
this.b = new B();
}
public int id { get; set; }
public string name { get; set; }
public B b { get; set; }
}
public class B
{
public string name { get; set; }
}
public class FJson : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
After reading (and testing) Paul Kiar & p.kaneman solution I'd say it seems to be a challenging task to implement WriteJson. Even though it works for the most cases - there are a few edge cases that are not covered yet.
Examples:
public bool ShouldSerialize*() methods
null values
value types (struct)
json converter attributes
..
Here is (just) another try:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (ReferenceEquals(value, null)) {
writer.WriteNull();
return;
}
var contract = (JsonObjectContract)serializer
.ContractResolver
.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties) {
if (property.Ignored) continue;
if (!ShouldSerialize(property, value)) continue;
var property_name = property.PropertyName;
var property_value = property.ValueProvider.GetValue(value);
writer.WritePropertyName(property_name);
if (property.Converter != null && property.Converter.CanWrite) {
property.Converter.WriteJson(writer, property_value, serializer);
} else {
serializer.Serialize(writer, property_value);
}
}
writer.WriteEndObject();
}
private static bool ShouldSerialize(JsonProperty property, object instance) {
return property.ShouldSerialize == null
|| property.ShouldSerialize(instance);
}
Json.NET does not have convenient support for converters that call JToken.FromObject to generate a "default" serialization and then modify the resulting JToken for output - precisely because the StackOverflowException due to recursive calls to JsonConverter.WriteJson() that you have observed will occur.
One workaround is to temporarily disable the converter in recursive calls using a thread static Boolean. A thread static is used because, in some situations including asp.net-web-api, instances of JSON converters will be shared between threads. In such situations disabling the converter via an instance property will not be thread-safe.
public class FJson : JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
t = JToken.FromObject(value, serializer);
}
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
Having done this, you can restore the [JsonConverter(typeof(FJson))] to your class A:
[JsonConverter(typeof(FJson))]
public class A
{
}
Demo fiddle #1 here.
A second, simpler workaround for generating a default JToken representation for a type with a JsonConverter applied takes advantage fact that a converter applied to a member supersedes converters applied to the type, or in settings. From the docs:
The priority of which JsonConverter is used is the JsonConverter defined by attribute on a member, then the JsonConverter defined by an attribute on a class, and finally any converters passed to the JsonSerializer.
Thus it is possible to generate a default serialization for your type by nesting it inside a DTO with a single member whose value is an instance of your type and has a dummy converter applied which does nothing but fall back to to default serialization for both reading and writing.
The following extension method and converter do the job:
public static partial class JsonExtensions
{
public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
{
if (value == null)
return JValue.CreateNull();
var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
var root = JObject.FromObject(dto, serializer);
return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
}
public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;
var dtoToken = new JObject(new JProperty("Value", token));
var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);
if (oldParent == null)
token.RemoveFromLowestPossibleParent();
return dto == null ? null : dto.GetValue();
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
interface IHasValue
{
object GetValue();
}
[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) { this.Value = value; }
public DefaultSerializationDTO() { }
[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }
object IHasValue.GetValue() { return Value; }
}
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
// By https://stackoverflow.com/users/3744182/dbc
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}
And then use it in FJson.WriteJson() as follows:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = serializer.DefaultFromObject(value);
// Remainder as before
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
The advantages and disadvantages of this approach are that:
It doesn't rely on recursively disabling the converter, and so works correctly with recursive data models.
It doesn't require re-implementing the entire logic of serializing an object from its properties.
It serializes to and deserializes from an intermediate JToken representation. It is not appropriate for use when attempt to stream a default serialization directly to and from a the incoming JsonReader or JsonWriter.
Demo fiddle #2 here.
Notes
Both converter versions only handle writing; reading is not implemented.
To solve the equivalent problem during deserialization, see e.g. Json.NET custom serialization with JsonConverter - how to get the "default" behavior.
Your converter as written creates JSON with duplicated names:
{
"id": 1,
"name": null,
"name": "value"
}
This, while not strictly illegal, is generally considered to be bad practice and so should probably be avoided.
I didn't like the solution posted above so I worked out how the serializer actually serialized the object and tried to distill it down to the minimum:
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract( value.GetType() );
writer.WriteStartObject();
foreach ( var property in contract.Properties )
{
writer.WritePropertyName( property.PropertyName );
writer.WriteValue( property.ValueProvider.GetValue(value));
}
writer.WriteEndObject();
}
No stack overflow problem and no need for a recursive disable flag.
I can't comment yet, so sorry for that...but I just wanted to add something to the solution provided by Paul Kiar. His solution really helped me out.
The code of Paul is short and simply works without any custom building of objects.
The only addition I would like to make is to insert a check if the property is ignored. If it is set to be ignored then skip the write for that property:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties)
{
if (property.Ignored)
continue;
writer.WritePropertyName(property.PropertyName);
writer.WriteValue(property.ValueProvider.GetValue(value));
}
writer.WriteEndObject();
}
By placing the attribute on class A, it is being called recursively. The first line in WriteJson override is again calling the serializer on class A.
JToken t = JToken.FromObject(value);
This causes a recursive call and hence the StackOverflowException.
From your code, I think you are trying to flatten the heirarchy. You can probably achieve this by putting the converter attribute on the property B, which will avoid the recursion.
//remove the converter from here
public class A
{
public A()
{
this.b = new B();
}
public int id { get; set; }
public string name { get; set; }
[JsonConverter(typeof(FJson))]
public B b { get; set; }
}
Warning: The Json you get here will have two keys called "name" one from class A and the other from class B.

Creating a custom converter to write json as a Dictionary<string, IObject> from an object of String Key IObject Value

I am trying to create a custom JsonConverter to follow a third party API with a rather complicated object structure, and am a bit hamstrung on a few things. Note I am using .NET 4.7.2, not Core.
I have source objects that look like this, which I have created as objects rather than dictionaries in order to easily integrate them with FluentValidation. The objects will represent json objects in the foreign API. The "Value" is an interfaced item which is eventually converted to one of about 20 concrete types.
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
The source json for such an object is a dictionary, whereby the Key is the property, and the value is one of the 20 object types, as so:
{
"checked": [
{
"elementType": "input",
"inputType": "checkbox",
"name": "another_checkbox",
"label": "Another checkbox",
"checked": true
}
]
}
In this case, the object in C# would look something like this:
PDItem item = new PDItem() {
Key = "checked",
Value = new List<IItem>() {
new ConcreteTypeA () {
elementType = "input",
inputType = "checkbox",
name = "another_checkbox",
label = "Another checkbox",
#checked = true
}
}
};
For reference, my "PDItem"s implement the following interfaces:
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem: IElement
{
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
I was able to convert some concrete types (without having to do any tricky dictionary to object conversions), below is a working example of the ElementConverter attacked to my IElement interface (IItem uses the same pattern, and the same JsonCreationConverter class):
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
case ElementTypeDescriptions.ThirdType:
return new ThirdType();
case ElementTypeDescriptions.FourthType:
case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What makes my scenario so tricky is not that I am converting objects to dictionaries and back, but that my object values are other concrete objects (interfaced). I want to write a custom JsonConverter to serialize and deserialize these objects, but have no idea how to read and write the json in the methods below, let alone if what I am attempting to do is even possible. Any assistance would be greatly appreciated!
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// DO STUFF
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// DO STUFF
}
}
EDITED PER DBC's request:
Apologies for the complicated question DBC, and I greatly appreciate your time! Obviously, I'm a bit new to posting to stack overflow (long time lurker as they say).
Below is full working code that will run in .net fiddle (or in a console application if you simply add a namespace and json.net 12.x packages to a new .NET 4.7.2 console project). Second apologies that it is still a bit long and complicated, It is actually greatly simplified thanks to the omission of the Validation code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem : IElement
{
string itemType { get; }
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
//case ElementTypeDescriptions.ThirdType:
// return new ThirdType();
//case ElementTypeDescriptions.FourthType:
//case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public class IItemConverter : JsonCreationConverter<IItem>
{
protected override IItem Create(Type objectType, JObject jObject)
{
switch (jObject["itemType"].Value<string>())
{
case ItemTypeDescriptions.FirstItemType:
return new FirstItemType();
case ItemTypeDescriptions.SecondItemType:
return new SecondItemType();
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ElementTypeDescriptions
{
public const string FirstType = "firstTypeId";
public const string SecondType = "secondTypeId";
public const string ThirdType = "thirdTypeId";
public const string FourthType = "fourthTypeId";
public const string FifthType = "fifthTypeId";
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ItemTypeDescriptions
{
public const string FirstItemType = "firstItemTypeId";
public const string SecondItemType = "secondItemTypeId";
}
/*** CONCRETE OBJECTS ***/
public class FirstType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string name { get; set; }
}
public class SecondType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string label { get; set; }
}
public class FirstItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string reference { get; set; }
}
public class SecondItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string database { get; set; }
}
/*** END CONCRETE OBJECTS ***/
public class PDItemConverter : JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class TestClass
{
public static void Test()
{
var json = GetJson();
var item = JsonConvert.DeserializeObject<PDItem>(json);
var json2 = JsonConvert.SerializeObject(item, Formatting.Indented);
Console.WriteLine(json2);
}
static string GetJson()
{
var json = #"{
""checked"": [
{
""elementType"": ""input"",
""inputType"": ""checkbox"",
""name"": ""another_checkbox"",
""label"": ""Another checkbox"",
""checked"": true
}
]
}
";
return json;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}
One of the easiest ways to write a JsonConverter is to map the object to be serialized to some DTO, then (de)serialize the DTO. Since your PDItem looks like a single dictionary key/value pair and is serialized like a dictionary, the easiest DTO to use would be an actual dictionary, namely Dictionary<string, List<IItem>>.
Thus your PDItemConverter can be written as follows:
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
// Convert to a dictionary DTO and serialize
serializer.Serialize(writer, new Dictionary<string, List<IItem>> { { value.Key, value.Value } });
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
// Deserialize as a dictionary DTO and map to a PDItem
var dto = serializer.Deserialize<Dictionary<string, List<IItem>>>(reader);
if (dto == null)
return null;
if (dto.Count != 1)
throw new JsonSerializationException(string.Format("Incorrect number of dictionary keys: {0}", dto.Count));
var pair = dto.First();
existingValue = hasExistingValue ? existingValue : new PDItem();
existingValue.Key = pair.Key;
existingValue.Value = pair.Value;
return existingValue;
}
}
Since you are (de)serializing using the incoming serializer any converters associated to nested types such as IItem will get picked up and used automatically.
In addition, in JsonCreationConverter<T> you need to override CanWrite and return false. This causes the serializer to fall back to default serialization when writing JSON as explained in this answer to How to use default serialization in a custom JsonConverter. Also, I don't recommend catching and swallowing JsonReaderException. This exception is thrown when the JSON file itself is malformed, e.g. by being truncated. Ignoring this exception and continuing can occasionally force Newtonsoft to fall into an infinite loop. Instead, propagate the exception up to the application:
public abstract class JsonCreationConverter<T> : JsonConverter
{
// Override CanWrite and return false
public override bool CanWrite { get { return false; } }
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Demo fiddle here.

Conditionally deserialize property based on JSON token type

In my API I would like to give consumers the option to send a property as either a string identifier or an object, for example:
{
"source": "token_xyz"
}
Or
{
"source": {
"name": "test"
}
}
Depending on the the type (string or object) I'd like to set a specific property on my class:
public class MyRequest
{
[JsonProperty("source")]
public SourceUser SourceUser { get; set; }
[JsonProperty("source")]
public string SourceToken { get; set; }
}
As you can see, I tried decorating both properties with JsonProperty("source") but unfortunately this does not work - the object fails to deserialize.
How can I deserialize to the appropriate property based on the JSON type?
Json.NET does not support having two properties with the same contract property name, possibly because, as explained in the JSON RFC:
An object whose names are all unique is interoperable in the sense
that all software implementations receiving that object will agree on
the name-value mappings. When the names within an object are not
unique, the behavior of software that receives such an object is
unpredictable.
Thus you will need some sort of custom JsonConverter to deserialize your "source" property. One easy way to do this is to apply the converter to a private Source property that sets & gets either SourceUser or SourceToken as appropriate:
public class MyRequest
{
[JsonConverter(typeof(SourceConverter))]
[JsonProperty]
object Source
{
get
{
// Possibly throw an exception if both are non-null?
return SourceUser ?? (object)SourceToken;
}
set
{
SourceUser = value as SourceUser;
SourceToken = value as string;
}
}
[JsonIgnore]
public SourceUser SourceUser { get; set; }
[JsonIgnore]
public string SourceToken { get; set; }
}
class SourceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter is intended to be applied with [JsonConverter(typeof(SourceConverter))]");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
else if (reader.TokenType == JsonToken.StartObject)
return serializer.Deserialize<SourceUser>(reader);
else
return (string)JValue.Load(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Sample fiddle.

JSON.NET Deserialize objects within object / array of objects

I have a situation where an API I'm using is returning inconsistent JSON, which I want to deserialize using JSON.NET. In one case, it returns an object that contains objects (note that the outer "1" can be any number):
{
"1":{
"0":{
"db_id":"12835424",
"title":"XXX"
},
"1":{
"db_id":"12768978",
"title":"YYY"
},
"2":{
"db_id":"12768980",
"title":"ZZZ"
},
"3":{
"db_id":"12768981",
"title":"PPP"
}
}
}
And in another case, it returns an array of objects:
{
"3":[
{
"db_id":"12769199",
"title":"XXX"
},
{
"db_id":"12769200",
"title":"YYY"
},
{
"db_id":"12769202",
"title":"ZZZ"
},
{
"db_id":"12769243",
"title":"PPP"
}
]
}
I have no idea why this inconsistency exists, but this is the format I'm working with. What would be the correct way to deserialize both formats with the JsonConvert.DeserializeObject method?
With the current version of Json.NET (Json.NET 4.5 Release 11), here is a CustomCreationConverter that will handle Json that deserializes sometimes as an object and sometimes as an array.
public class ObjectToArrayConverter<T> : CustomCreationConverter<List<T>> where T : new()
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
List<T> target = new List<T>();
try
{
// Load JObject from stream
JArray jArray = JArray.Load(reader);
// Populate the object properties
serializer.Populate(jArray.CreateReader(), target);
}
catch (JsonReaderException)
{
// Handle case when object is not an array...
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T t = new T();
// Populate the object properties
serializer.Populate(jObject.CreateReader(), t);
target.Add(t);
}
return target;
}
public override List<T> Create(Type objectType)
{
return new List<T>();
}
}
Example Usage:
[JsonObject]
public class Project
{
[JsonProperty]
public string id { get; set; }
// The Json for this property sometimes comes in as an array of task objects,
// and sometimes it is just a single task object.
[JsonProperty]
[JsonConverter(typeof(ObjectToArrayConverter<Task>))]
public List<Task> tasks{ get; set; }
}
[JsonObject]
public class Task
{
[JsonProperty]
public string name { get; set; }
[JsonProperty]
public DateTime due { get; set; }
}
I think this is something that should be possible by creating a JsonCreationConverter. This article can probably help out: http://dotnetbyexample.blogspot.nl/2012/02/json-deserialization-with-jsonnet-class.html

Handle errors or inconsistencies when parsing JSON using JSON.NET [duplicate]

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
{
}

Resources