JSON.Net (De)Serialize Untyped Property

JSON.net (de)serialize untyped property

If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto,
then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.

The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:

public class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}

abstract class TypeWrapper
{
protected TypeWrapper() { }

[JsonIgnore]
public abstract object ObjectValue { get; }

public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}

sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }

public TypeWrapper(T value)
: base()
{
this.Value = value;
}

public override object ObjectValue { get { return Value; } }

public T Value { get; set; }
}

Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:

public class Example
{
public int TypedProperty { get; set; }
[JsonConverter(typeof(UntypedToTypedValueConverter))]
public object UntypedProperty { get; set; }
}

If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:

public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();

// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedPropertyContractResolver instance;

// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }

public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }

protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);

foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.PropertyType == typeof(object)
&& property.Converter == null)
{
property.Converter = property.MemberConverter = converter;
}
}
return contract;
}
}

And use it as follows:

var settings = new JsonSerializerSettings 
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};

var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);

var example2 = JsonConvert.DeserializeObject<Example>(json, settings);

In both cases the JSON created looks like:

{
"TypedProperty": 5,
"UntypedProperty": {
"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
"Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
}
}

How to deserialize an untyped object using JSON.NET or DataContractJsonSerializer

Json.Net has a TypeNameHandling enum which can be specified in serializer settings to do what you want (see documentation here). It sounds like you want TypeNameHandling.All.

Specifically, try:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var serialized = JsonConvert.SerializeObject(value, settings);
var deserialized = JsonConvert.DeserializeObject(value, settings);

Of course, this requires that the type in question be available in both the serializing and deserializing application. If not, Json.Net can always deserialize to a JObject or IDictionary<string, object>, allowing the values to be accessed dynamically.

Deserialize a json object with different structure and same name

Thank you @Piotr. It completely worked. because your first part of the answer was not correct for me, I rewrite your response as an answer.

as you said the correct answer was in this link.

https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n

So I made this class.

class JsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<T>);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);

if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}

public override bool CanWrite
{
get { return false; }
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

and changed my Movie Class to this.

internal class ImdbJsonMovie
{
public string Url { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("image")]
public string Image { get; set; }

[JsonProperty("genre")]
[JsonConverter(typeof(JsonConverter<string>))]
public List<string> Genre { get; set; }

[JsonProperty("contentRating")]
public string ContentRating { get; set; }

[JsonProperty("actor")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Actor { get; set; }

[JsonProperty("director")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Director { get; set; }

[JsonProperty("creator")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Creator { get; set; }
}

and this Enum

public class ImdbJsonTypeEnum
{
[JsonProperty("@type")]
public TypeEnum Type { get; set; }

[JsonProperty("url")]
public string Url { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

public enum TypeEnum
{
Organization,
Person
};
}

It worked for one director and multi director movies.

Thank you

Json.Net Deserialize an object dynamically?

You can do that with:

JsonConvert.DeserializeObject(string value, Type type)

This will deserialize the JSON using the type information rather than a generic type parameter.

var myObjects = new List<MyObject>
{
new MyObject
{
ClassType = typeof(Cat).FullName,
Body = JsonConvert.SerializeObject(new Cat { Fluffiness = 10 })
},
new MyObject
{
ClassType = typeof(Dog).FullName,
Body = JsonConvert.SerializeObject(new Dog { Loudness = 3 })
}
};

List<object> objects =
myObjects
.Select(myObject =>
JsonConvert.DeserializeObject(
myObject.Body,
typeof(Cat).Assembly.GetType(myObject.ClassType)))
.ToList();

See:

  • JsonConvert.DeserializeObject

JSON.net serializing untyped list as typed list?

Assuming your class looks something like this:

public class ReportDataSource 
{
public string Name { get; set; }
public ICollection Data { get; set; }
}

You can do it with an appropriate JsonConverter:

public sealed class TypedToTypelessCollectionConverter : JsonConverter
{
[ThreadStatic]
static Type itemType;

public static IDisposable SetItemType(Type deserializedType)
{
return new ItemType(deserializedType);
}

sealed class ItemType : IDisposable
{
Type oldType;

internal ItemType(Type type)
{
this.oldType = itemType;
itemType = type;
}

int disposed = 0;

public void Dispose()
{
// Dispose of unmanaged resources.
if (Interlocked.Exchange(ref disposed, 1) == 0)
{
// Free any other managed objects here.
itemType = oldType;
oldType = null;
}
// Suppress finalization. Since this class actually has no finalizer, this does nothing.
GC.SuppressFinalize(this);
}
}

public override bool CanConvert(Type objectType)
{
return objectType == typeof(ICollection);
}

public override bool CanWrite { get { return false; }}

public override bool CanRead { get { return itemType != null; } }

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize(reader, typeof(List<>).MakeGenericType(itemType));
}

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> GetEnumerableTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
yield return intType.GetGenericArguments()[0];
}
}
}
}

And then use it like:

public class ReportDataSource 
{
public string Name { get; set; }

[JsonConverter(typeof(TypedToTypelessCollectionConverter))]
public ICollection Data { get; set; }

public static ReportDataSource Deserialize(ReportDataSource dataSourceFromDb, string json)
{
using (TypedToTypelessCollectionConverter.SetItemType(dataSourceFromDb == null || dataSourceFromDb.Data == null ? null : dataSourceFromDb.Data.GetType().GetEnumerableTypes().SingleOrDefault()))
{
return JsonConvert.DeserializeObject<ReportDataSource>(json);
}
}
}

JSON deserialization error when property name is missing

The problem here is the JSON response is actually an array of mixed types. The first element of the array is a string, and the second element is an array of event objects. You will need a custom JsonConverter to deserialize this JSON.

Here is the code you would need for the converter:

class ServiceResponceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ServiceResponce));
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray ja = JArray.Load(reader);
ServiceResponce resp = new ServiceResponce();

resp.Events = ja[1].ToObject<Event[]>(serializer);

return resp;
}

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Then, add a [JsonConverter] attribute to the ServiceResponce class to tie it to the converter:

[JsonConverter(typeof(ServiceResponceConverter))]
public class ServiceResponce
{
public Event[] Events { get; set; }
}

Now you can deserialize to the ServiceResponce class as normal and it will work properly.

Optional: If you also want to capture the "Successful Request: 96 Results" string from the response, add

public string ResultString { get; set; }

to the ServiceResponce class and add the following line to the the ReadJson method of the converter:

resp.ResultString = (string)ja[0];

Working demo here: https://dotnetfiddle.net/opPUmX



Related Topics



Leave a reply



Submit