Asp.Net Web Api & JSON.Net - Deserialize body into .Net generic objects - json

I want to deserialize the body into .net classes (dictionary and list).
(so my code is transparent for xml and json)
public HttpResponseMessage Post([FromBody]IDictionary<string, object> body)
{
}
Currently I use a converter to handle nested dictionary deserialization.
class IDictionaryConverter : CustomCreationConverter<IDictionary<string, object>>
{
public override IDictionary<string, object> Create(Type objectType)
{
return new Dictionary<string, object>();
}
public override bool CanConvert(Type objectType)
{
// in addition to handling IDictionary<string, object>
// we want to handle the deserialization of dict value
// which is of type object
return objectType == typeof(object) || base.CanConvert(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.Null)
return base.ReadJson(reader, objectType, existingValue, serializer);
// if the next token is not an object
// then fall back on standard deserializer (strings, numbers etc.)
return serializer.Deserialize(reader);
}
}
But nested JLists are not converted to List.
Maybe I can create a IListConverter, but is there an better way to do this?
I want in my web api only one code. not: if json do that, if xml do that...

This solves my problem:
class IDictionaryConverter : CustomCreationConverter<IDictionary<string, object>>
{
public override IDictionary<string, object> Create(Type objectType)
{
return new Dictionary<string, object>();
}
public override bool CanConvert(Type objectType)
{
// in addition to handling IDictionary<string, object>
// we want to handle the deserialization of dict value
// which is of type object
return objectType == typeof(object) || base.CanConvert(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.Null)
return base.ReadJson(reader, objectType, existingValue, serializer);
if (reader.TokenType == JsonToken.StartArray)
return serializer.Deserialize<IList<object>>(reader);
// if the next token is not an object
// then fall back on standard deserializer (strings, numbers etc.)
return serializer.Deserialize(reader);
}
}

Related

How to define SingleOrArrayConverter to Handle JSON property in F# ( Port from C# )

I am porting some ( well tested over time ) code from C# to F# and having some issues getting something to work in F#
The C# Code:
( Object I want to serialise )
public class Locality
{
public string category { get; set; }
public int id { get; set; }
public string location { get; set; }
public string postcode { get; set; }
public string state { get; set; }
public double? latitude { get; set; }
public double? longitude { get; set; }
}
public class Localities
{
[JsonProperty("locality")]
[JsonConverter(typeof(R2H.Models.JSon.SingleOrArrayConverter<Locality>))]
public List<Locality> locality { get; set; }
}
public class AuspostPostCodeLocality
{
public Localities localities { get; set; }
}
( JSON converter )
public class SingleOrArrayConverter<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();
}
}
( Attempt at F# Code )
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType : Type) =
objectType = typeof<List<'T>>
override this.ReadJson(reader : JsonReader, objectType : Type, existingValue : System.Object, serializer : JsonSerializer) =
let mutable (token : JToken) = JToken.Load (reader)
if token.Type = JTokenType.Array then
(token.ToObject<List<'T>> ())
else
([|(token.ToObject<'T> ())|])
override this.CanWrite
with get() =
false
override this.WriteJson(writer : JsonWriter, value : System.Object, serializer : JsonSerializer) =
raise (new NotImplementedException() :> System.Exception)
And my attempt at the model ( You can see several attempts commented out ).
type Locality = {
category: string
id: int
location: string
postcode: int
state: string
latitude: decimal
longitude: decimal
}
type Localities = {
//inherit JsonConverter<SingleOrArrayConverter<Locality>>()
//[<JsonProperty("locality");JsonConverter<SingleOrArrayConverter<Locality>>>]
//[<JsonConverter(typeof (SingleOrArrayConverter<Locality>))>]
//[<JsonProperty("locality")>]
locality: List<Locality>
}
type PostCodeLocality = {
localities : Localities
}
Since your converter seems to have originated from this answer by Brian Rogers to How to handle both a single item and an array for the same property using JSON.net, I assume that you are trying to create a generic converter for mutable lists of type System.Collections.Generic.List<'T> (abbreviated to ResizeArray in FSharpx.Collections).
F# also has an immutable list type FSharp.Collections.List<'T> abbreviated to list. Be sure which one you want to use.[1]
With that in mind, assuming you want System.Collections.Generic.List<'T> your converter can be written as follows:
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<ResizeArray<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) = // Unlike in C# it's not necessary to declare the types of the arguments when there is no ambiguity
let token = JToken.Load (reader)
if token.Type = JTokenType.Array then
// Upcast to obj as upcasting is not automatic for returned value
// https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/casting-and-conversions#upcasting
(token.ToObject<ResizeArray<'T>> ()) :> obj
else
ResizeArray [|(token.ToObject<'T> ())|] :> obj // Convert array to List<T> then upcast to object
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
And your Localities type defined as follows:
type Localities = {
[<JsonProperty("locality")>]
[<JsonConverter(typeof<SingleOrArrayConverter<Locality>>)>] // Fix syntax: typeof<'T> not typeof(T)
locality: ResizeArray<Locality> // Be sure whether you want System.Collections.Generic.List<'T> a.k.a ResizeArray<'T> or FSharp.Collections.List<'T>
}
Demo fiddle #1 here.
If you do want to use f#'s immutable list, define your converter as follows:
type SingleOrArrayFSharpListConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<list<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) = // Unlike in C# it's not necessary to declare the types of the arguments when there is no ambiguity
let token = JToken.Load (reader)
if token.Type = JTokenType.Array then
// Upcast to obj as upcasting is not automatic for returned value
// https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/casting-and-conversions#upcasting
(token.ToObject<list<'T>> ()) :> obj
else
[token.ToObject<'T>()] :> obj // Convert array to list<T> then upcast to object
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
And modify Localities as follows:
type Localities = {
[<JsonProperty("locality")>]
[<JsonConverter(typeof<SingleOrArrayFSharpListConverter<Locality>>)>] // Fix syntax: typeof<'T> not typeof(T)
locality: Locality list // Be sure whether you want System.Collections.Generic.List<'T> a.k.a ResizeArray<'T> or FSharp.Collections.List<'T>
}
Demo fiddle #2 here.
Update
It isn't actually necessary to preload the JSON into a JToken to check to see whether it is an array, you can just check the value of JsonReader.TokenType. The following version of SingleOrArrayConverter<'T> does this and so should be more efficient:
module JsonExtensions =
type JsonReader with
member r.ReadAndAssert() =
if not (r.Read()) then raise (JsonReaderException("Unexpected end of JSON stream."))
r
member r.MoveToContentAndAssert() =
if r.TokenType = JsonToken.None then r.ReadAndAssert() |> ignore
while r.TokenType = JsonToken.Comment do r.ReadAndAssert() |> ignore
r
open JsonExtensions
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<ResizeArray<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) =
let a = if (existingValue :? ResizeArray<'T>) then existingValue :?> ResizeArray<'T> else ResizeArray<'T>() // Reuse the incoming List<T> if preallocated
match reader.MoveToContentAndAssert().TokenType with
| JsonToken.StartArray -> serializer.Populate(reader, a)
| _ -> a.Add(serializer.Deserialize<'T>(reader))
a :> obj
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
Demo fiddle #3 here.
[1] For details see Creating a generic List <T> in F#.

Deserialize a JSON array as Dictionary with JSON.Net

I've one part of my JSON that looks like this:
Like you can see, in the JSON, the temperature "dictionary", is in fact a list of list of 2 element.
The first element is a timestamp, the second the temperature. Not sure why the provider of the service did it like this, but I don't really have the choice, I must do with it.
But in my C# object, I would like to have this as a dictionary, with timestamp as the key, and temperature as the value.
Is this possible?
//note, I've a custom converter that converts from long to DateTime
public Dictionary<DateTime, double> Temperature { get; set; }
and deserializing like this:
JsonConvert.DeserializeObject<List<WeatherPredictionDay>>(content, new EpochConverter());
Yes, this can be done with a custom JsonConverter like this:
class TemperatureArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Dictionary<DateTime, double>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray ja = JArray.Load(reader);
var dict = new Dictionary<DateTime, double>();
foreach (JArray item in ja)
{
var key = item[0].ToObject<DateTime>(serializer);
var val = item[1].ToObject<double>(serializer);
dict.Add(key, val);
}
return dict;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use it, just mark your Temperature property with a [JsonConverter] attribute like this:
[JsonConverter(typeof(TemperatureArrayConverter))]
public Dictionary<DateTime, double> Temperature { get; set; }
Note: the above converter as written is intended to work with your existing EpochConverter to convert the timestamp values into DateTimes for the dictionary keys.
Here is a working demo: https://dotnetfiddle.net/TdxYjj

Deserialize an object into another similar object of another type json.net

I have a question similar to Cannot deserialize JSON array into type - Json.NET, but I still get errors.
So, I have 3 classes:
public class Class1
{
public string[] P2 { get; set; }
}
public class Class2
{
public Wrapper<string>[] P2 { get; set; }
}
public class Wrapper<T>
{
public T Value { get; set; }
}
I am trying to serialize Class 2 into string and back into Class 1.
Here's how:
Class2 c2 = new Class2
{
P2 = new Wrapper<string>[]
{
new Wrapper<string> { Value = "a" },
new Wrapper<string> { Value = "a" },
},
};
string s = JsonConvert.SerializeObject(c2);
Class1 c1 = (Class1)JsonConvert.DeserializeObject(s, typeof(Class1), new FormatConverter());
FormatConverter class is defined below:
public class FormatConverter : 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)
{
if (objectType == typeof(string[]))
{
List<string> list = new List<string>();
while (reader.Read())
{
if (reader.TokenType != JsonToken.StartObject)
{
continue;
}
Wrapper<string> obj = (Wrapper<string>)serializer.Deserialize(reader, typeof(Wrapper<string>));
list.Add(obj.Value);
}
return list.ToArray();
}
}
public override bool CanConvert(Type type)
{
if (type == typeof(string[]))
{
return true;
}
return false;
}
}
What am I missing? I get following exception:
An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
Additional information: Unexpected end when deserializing object. Path '', line 1, position 46.
Thanks,
Alex
I managed to find the answer myself. Here's how:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (type == typeof(string[]) && reader.TokenType == JsonToken.StartArray)
{
//remove converter not to trigger a recursive call
var converter = serializer.Converters[0];
serializer.Converters.RemoveAt(0);
//deserialization into correct type
Wrapper<string>[] obj = (Wrapper<string>[])serializer.Deserialize(reader, typeof(Wrapper<string>[]));
//restore converter
serializer.Converters.Add(converter);
if (obj != null)
{
return obj.Select(w => w == null ? null : w.Value).ToArray();
}
return reader.Value;
}
}

How to get the name of the property for which the custom deserialization (ReadJson) is invoked?

I have a suitation. I want to do a custom Deserialization for some properties. But i want to handle it in a single convertor class. Dont want to write seperate for each class.
So is there any way to find the Property Name for which the ReadJson is invoked?
// My Class
public class SomeClass
{
// Private members
private double m_nValue;
private string m_strValue;
// Properties
[JsonConverter(typeof(AlfhaConverter))]
public double Value
{
get { return m_nValue; }
set { m_nValue = value; }
}
[JsonConverter(typeof(AlfhaConverter))]
public string StrValue
{
get { return m_strValue; }
set { m_strValue = value; }
}
}
// JsonConverter
public class PropertyConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//Here i want to find the property name so that i can perform certain steps based on the property
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Deserializing self-tracking entity with nested TrackableCollection

How can I deserialize JSON string into entity of type like this (self-tracking properties removed to be simple):
public class User:
{
int Id { get; set; }
string Name { get; set; }
public TrackableCollection<Role> Roles { get; set; } // <!
}
Role is also simple class with two properties. TrackableCollection is descendant of Collection (System.Collections.ObjectModel).
So I want: having JSON string like this
{"Id":0, "Name":"Test User", "Roles": [{"Id":1, "Name": "Role 1"}, {"Id":2, "Name": "Role 2"}, {"Id":3, "Name": "Role 3"}]}
get entity with correctly deserialized Roles collection.
Ok, seems like nobody was interested this question, anyway here is the solution. This class serializes and deserializes self-tracking POCO entities including all nested TrackableCollections and objects.
Please notice about SupportedTypes method. I added the IEntity interface (blank inside) and modified my T4 template on this line:
<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#><#=(entity.BaseType == null ? ": " : ", ") + "IEntity" #>, IObjectWithChangeTracker, INotifyPropertyChanged
You can do nothing about IEntity. Just write the SupportedTypes method as you need.
I also commented the [DataMember] attribute in the template above the ChangeTracker property:
//[DataMember]
public ObjectChangeTracker ChangeTracker
Anyway this is not important. Take the EntityConverter and enjoy.
/// <summary>
/// Serializes self-tracking POCO entities with DataMemberAttribute marked properties.
/// </summary>
public class EntityConverter : JavaScriptConverter
{
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (obj != null)
{
var properties = obj.GetType().GetProperties().Where(prop => prop.HasAttibute(typeof(DataMemberAttribute)));
foreach (var property in properties)
{
object value = property.GetValue(obj, null);
// Serialize nested TrackableCollection object
if (property.PropertyType.Name.Equals(typeof(TrackableCollection<object>).Name))
value = SerializeCollection((value as IEnumerable).Cast<object>(), serializer);
result.Add(property.Name, value);
}
}
return result;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
var entity = Activator.CreateInstance(type);
foreach (KeyValuePair<string, object> kvp in dictionary)
{
PropertyInfo property = type.GetProperty(kvp.Key);
if ((property != null) && (property.HasAttibute(typeof(DataMemberAttribute))))
{
object value = default(object);
if (!property.PropertyType.Name.Equals(typeof(TrackableCollection<object>).Name))
{
// If property is not a TrackableCollection object
// http://stackoverflow.com/questions/793714/how-can-i-fix-this-up-to-do-generic-conversion-to-nullablet
Type u = Nullable.GetUnderlyingType(property.PropertyType);
string jsonValue = kvp.Value != null ? kvp.Value.ToString() : null;
dynamic dynamicVal;
if (u != null)
dynamicVal = jsonValue == "null" ? null : Convert.ChangeType(jsonValue, u);
else if (kvp.Value is IDictionary<string, object>)
dynamicVal = Deserialize(kvp.Value as IDictionary<string, object>, property.PropertyType, serializer);
else
dynamicVal = Convert.ChangeType(jsonValue, property.PropertyType);
value = dynamicVal;
}
else
{
// If property is a TrackableCollection object
var dictionaries = (kvp.Value as IEnumerable).Cast<IDictionary<string, object>>();
value = DeserializeCollection(dictionaries, property.PropertyType, serializer);
}
property.SetValue(entity, value, null);
}
}
return entity;
}
/// <summary>
/// Serializes TrackableCollection
/// </summary>
protected IList<IDictionary<string, object>> SerializeCollection(IEnumerable<object> collection, JavaScriptSerializer serializer)
{
var result = new List<IDictionary<string, object>>();
foreach (object obj in collection)
{
result.Add(Serialize(obj, serializer));
}
return result;
}
/// <summary>
/// Deserializes TrackableCollection
/// </summary>
protected object DeserializeCollection(IEnumerable<IDictionary<string, object>> dictionaries, Type propertyType, JavaScriptSerializer serializer)
{
object collection = Activator.CreateInstance(propertyType); // TrackableCollection<T>
Type genericType = propertyType.GetGenericArguments()[0]; // T
MethodInfo addMethod = collection.GetType().GetMethod("Add"); // Add(T object)
foreach (var dic in dictionaries)
{
addMethod.Invoke(collection, new [] { Deserialize(dic, genericType, serializer) });
}
return collection;
}
/// <remarks>
/// http://stackoverflow.com/questions/159704/how-to-implement-custom-json-serialization-from-asp-net-web-service
/// </remarks>
public override IEnumerable<Type> SupportedTypes
{
get
{
IList<Type> result = new List<Type>();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
AssemblyBuilder dynamicAssemblyCheck = assembly as AssemblyBuilder;
if (dynamicAssemblyCheck == null)
{
try
{
foreach (Type type in assembly.GetExportedTypes())
{
if ((type != typeof(IEntity)) && typeof(IEntity).IsAssignableFrom(type))
{
result.Add(type);
}
}
}
catch(Exception){} // bad practice, i know, i know
}
}
return result;
}
}
}