I am consuming some JSON in a C# console application and for some of the data, there is an array of options. Example JSON:
{
\"FIELD_NAME\": \"Survey\",
Json.NET will throw an exception when the expected JSON value type (array, collection or primitive) does not match the observed value type. Since, in the case of your List<Option> OPTIONS
, you want unexpected value types to be skipped, you will need to create a custom JsonConverter such as the following:
public class TolerantCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string) || objectType.IsArray)
return false;
return objectType.GetCollectionItemTypes().Count() == 1;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.StartArray)
{
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else
{
reader.Skip();
return existingValue;
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class TypeExtensions
{
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type> GetCollectionItemTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
yield return intType.GetGenericArguments()[0];
}
}
}
}
Then apply it to Field
as follows:
public class Field
{
public string FIELD_NAME { get; set; }
public string VALUE { get; set; }
public int FIELD_ID { get; set; }
[JsonConverter(typeof(TolerantCollectionConverter))]
public List<Option> OPTIONS { get; set; }
}
Or use it for all collections via JsonSerializerSettings:
var settings = new JsonSerializerSettings
{
Converters = { new TolerantCollectionConverter() },
};
var obj = JsonConvert.DeserializeObject<Field>(stringTest, settings);
Notes:
The converter only works for collections that are writable, since it allocates the collection first and then populates it. For read-only collections or arrays you need to populate a List<T>
first then allocate the read-only collection or array from it.
My assumption here is that you want to ignore an empty object when an array value is expected. If instead you want to deserialize the object into a collection item then add that to the returned collection you could use SingleOrArrayConverter<T>
from How to handle both a single item and an array for the same property using JSON.net.
The root JSON container shown in your question is an object -- an unordered set of name/value pairs that begins with {
and ends with }
-- rather than an array. Thus you need to deserialize it as a Field
not a List<Field>
.
Sample fiddle.