Json.net deserialize interface, concrete class uses a JsonConverter - interface

My problem is that my JsonConverter doesn't seem to get invoked by the json.net de-serialization process when the converter is applied to an implementation of an interface, and the propertytype is the interface.
I use TypeNameHandling = TypeNameHandling.Objects to add $type to the json. I do so both on serialization and on de-serialization.
And when I have a property that is of an implementation of the interface the class' converter is invoked properly.
But when I have a property of interface type, the concrete class' converter is not invoked.
When I deserialize this class my JsonDataBagCreationConverter will be invoked by the RealTelephone but not by the Telephone because this is an interface.
Even though they are both serialized with the correct $type.
This results in RealTelephone having its .Data filled whereas Telephones .Data is null.
[JsonConverter(typeof(JsonDataBagCreationConverter<ContainerForITelephone>))]
public class ContainerForITelephone : IDataBag
{
private object _data;
private DataBagTypeEnum _dataBagTypeEnum;
public ITelephone Telephone { get; set; }
public Telephone RealTelephone { get; set; }
public object Data
{
get { return _data; }
set { _data = value; }
}
public DataBagTypeEnum DataBagType_Enum
{
get { return _dataBagTypeEnum; }
}
}
This jsonconverter is not invoked for the Telephone property. But it is for RealTelephone.
public class JsonDataBagCreationConverter<T> : JsonConverter where T : IDataBag, new()
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.Null)
{
var jsonObject = JObject.Load(reader);
var target = Create(objectType, jsonObject);
serializer.Populate(jsonObject.CreateReader(), target);
((IDataBag)target).Data = jsonObject.ToString();
return target;
}
return null;
}
}
[JsonConverter(typeof(JsonDataBagCreationConverter<Telephone>))]
public class Telephone : ITelephone
{
public string Name { get; set; }
public string AreaCode { get; set; }
public string Number { get; set; }
public SubPhone SubPhone { get; set; }
public object Data { get; set; }
public DataBagTypeEnum DataBagType_Enum { get; set; }
}
I look forward to hearing from you, thanks
Jan

SOLVED:
public class JsonDataBagCreationConverter<T> : JsonConverter where T:IDataBag
{
//, new() prevented us from using interfaces. Activator.CreateInstance did the trick in Create
//Used when the object decorated with [JsonConverter(typeof(JsonDataBagCreationConverter<xxxx>))] is de-serialized
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
if (objectType.IsInterface)
{
// Interfaces cannot be instantiated but must be converted to their "real" implemented type
// Because we serialize with settings.TypeNameHandling = TypeNameHandling.Objects;
// A $type property is added to the json by the deserializer.
string type = jsonObject["$type"].ToString();
var typesAsArray = type.Split(',');
var wrappedTarget = Activator.CreateInstance(typesAsArray[1], typesAsArray[0]);
var realTarget = wrappedTarget.Unwrap() as IDataBag;
serializer.Populate(jsonObject.CreateReader(), realTarget); // Will call this function recursively for any objects that have JsonDataBagCreationConverter as attribute
((IDataBag)realTarget).Data = jsonObject.ToString(); // This is where custom data is stored in databag
return realTarget;
}
// Non interface
var target = Create(objectType, jsonObject);
serializer.Populate(jsonObject.CreateReader(), target); // Will call this function recursively for any objects that have JsonDataBagCreationConverter as attribute
((IDataBag)target).Data = jsonObject.ToString(); // This is where custom data is stored in databag
return target;
}
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return false;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new Exception("WriteJson not implemented");
}
protected IDataBag Create(Type objectType, JObject jsonObject)
{
var aa = Activator.CreateInstance(objectType);
return aa as IDataBag;
// return new T(); // this demands ,new() on the class and then it will not work with interfaces
}
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
}

Related

EF6 - Get entity for DbUpdateCommandTree in DbCommandTreeInterceptor

I am trying to get the value of a "NotMapped" property for a Entity/class when intercepting a DbUpdateCommandTree.
I have looked through the various metadata, but I cannot find the "link" to the Entity from the CommandTree, so unfortunately I am stuck.
Is it even possible ?
public class SomeEntity
{
public int ID { get; set; }
[NotMapped]
public int SomeUnmappedProperty { get; set; }
}
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext ctx)
{
if (ctx.OriginalResult.DataSpace == DataSpace.SSpace)
{
var updateCommand = ctx.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
// I would like to get a value of a specific property here.
// Pseudo code
var val = updateCommand.Entity.GetPropertyValue("SomeUnmappedProperty") as int;
}
}
}
}

Entity framework Generic query in Nongeneric Property

In Entity framework I have objectsets like
public partial class Building
{
public int BuildingID { get; set; }
public string BuildingName { get; set; }
}
public partial class Town
{
public int TownID { get; set; }
public string TownName { get; set; }
}
I want to create a generic query like
T.OrderBy(o=>o.Id).Skip(maxDispItem * (page - 1)).Take(maxDispItem).ToList();
T is generic class can be Building or Town but problem is BuildingId and TownId has different name.I don't want to change their name as Id and create interface IIdentity.
Maybe you could try something like this:
var query = (typeof(T) == typeof(Building) ?
context.Buildings.Select(b => new { Id = b.BuildingId, Name = b.BuildingName }) :
context.Towns.Select(t => new { Id = t.TownId, Name = b.TownName }))
.OrderBy(o => o.Id)...
Not tested but that's worth a test...
You can create generic method which find a field decorated with KeyAttribute, and then performs sorting by found key field. I have tested your model, works perfectly. Look at code snippet.
DbContext:
using System.Collections.Generic;
using System.Data.Entity;
namespace ConsoleApplication28.Entities
{
public class AppDbContext : DbContext
{
public AppDbContext()
{
Database.Connection.ConnectionString = #"Data Source=NOTEBOOK-PC;Initial Catalog=StackOverflowTest;Integrated Security=True";
Database.SetInitializer(new AppDbInitializer());
}
public DbSet<Town> Towns { get; set; }
public DbSet<Building> Buildings { get; set; }
}
public class AppDbInitializer : DropCreateDatabaseIfModelChanges<AppDbContext>
{
protected override void Seed(AppDbContext context)
{
context.Buildings.AddRange(new List<Building>
{
new Building {BuildingName = "Building1"},
new Building {BuildingName = "Building2"},
});
context.Towns.AddRange(new List<Town>
{
new Town {TownName = "Town1"},
new Town {TownName = "Town2"},
});
context.SaveChanges();
base.Seed(context);
}
}
}
Building
using System.ComponentModel.DataAnnotations;
namespace ConsoleApplication28.Entities
{
public class Building
{
[Key]
public int BuildingID { get; set; }
public string BuildingName { get; set; }
}
}
Town
using System.ComponentModel.DataAnnotations;
namespace ConsoleApplication28.Entities
{
public class Town
{
[Key]
public int TownID { get; set; }
public string TownName { get; set; }
}
}
Program
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using ConsoleApplication28.Entities;
using System.ComponentModel.DataAnnotations;
namespace ConsoleApplication28
{
class Program
{
static void Main(string[] args)
{
const int maxDispItem = 10;
const int page = 1;
var db = new AppDbContext();
var towns = db.Towns.OrderByKey().Skip(maxDispItem * (page - 1)).Take(maxDispItem).ToList();
var buildings = db.Buildings.OrderByKey().Skip(maxDispItem * (page - 1)).Take(maxDispItem).ToList();
}
}
public static class Extensions
{
/// <summary>
/// Sorts the elements of a sequence in ascending order according to a key specified using KeyAttribute
/// </summary>
public static IOrderedQueryable<T> OrderByKey<T>(this IQueryable<T> source, bool isAsc = true)
{
var type = typeof(T);
var keyProperty = type.GetProperties().Single(x => x.GetCustomAttributes(typeof(KeyAttribute)).Any());
return source.OrderBy(keyProperty.Name, isAsc);
}
#region COPIED FROM THERE http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property, bool isAsc)
{
return isAsc ? source.OrderBy(property) : source.OrderByDescending(property);
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IOrderedQueryable<T>)result;
}
#endregion
}
}

How to access to the object array

I have an auto generated class from an xml like the following:
public partial class XmlClass {
private decimal num1;
private ClassA[] classField;
/// <remarks/>
public decimal num1 {
get;
set;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("classA")]
public ClassA[] classA {
get{...};
set{...};
}
}
public partial class ClassA {
private object[] itemsField;
private string typeField;
[System.Xml.Serialization.XmlElementAttribute("commands", typeof(classACommands))]
[System.Xml.Serialization.XmlElementAttribute("minVersion", typeof(string))]
public object[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string type {
get {
return this.typeField;
}
set {
this.typeField = value;
}
}
}
ClassA has the string and commands class as Objects in the Object[]. I can see everything is deserialized perfectly and get them by: (commands)myXmlClass.classA.ElementAt(i).Items[3], where i is from the index of the ClassA array. But how can I get or set them without using '3'? It might be different in different ClassA elements.
Not sure I totally understand your question, but you can use a foreach loop:
foreach(ClassA a in myXmlClass.classA) {
Console.WriteLine(a.num1.ToString());
}

Use a base class as the return type in ApiController

Using the ASP.NET WebApi 4 RC, is it possible to have an ApiController's return type be a base class and actually return instances of derived classes? Trying to do this now results in an internal server error (500) when returning xml. Returning json using this method works correctly.
public class Base
{
public int ID { get; set; }
}
public class Derived : Base
{
public string Message { get; set; }
}
public class ValuesController : ApiController
{
public IEnumerable<Base> Get()
{
return new Derived[] {
new Derived(){ Message="test"},
new Derived(){ Message="another"}
};
}
}
It would seem that the XML serialization is what's throwing the error but all I can see is the generic 500 error.
Yes, you need to use the knowntype serialization hint:
[System.Runtime.Serialization.KnownType(typeof(Derived))]
public class Base
{
public int ID { get; set; }
}
You might want to do it programmatically
private static Type[] GetKnownType()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var knownTypes = new List<Type>();
foreach (var assembly in assemblies)
{
knownTypes.AddRange(assembly.GetTypes().Where(x => x.BaseType == typeof (BaseResponse)).ToArray());
}
return knownTypes.ToArray();
}
Do remember your child class MUST have a default constructor else you will get runtime serialization error.

Serializability of enum-like class

I need to access an enum through a webservice.
As a webservice allocates 0 based integers to an enumeration (ignoring preset values in enum definition), I built the following:
public class StatusType
{
public StatusVal Pending { get { return new StatusVal( 1, "Pending"); } }
public StatusVal Authorised { get { return new StatusVal(2, "Authorised"); } }
public StatusVal Rejected { get { return new StatusVal(3, "Rejected"); } }
public StatusVal Sent { get { return new StatusVal(4, "Sent"); } }
public StatusVal InActive { get { return new StatusVal(5, "InActive"); } }
public List<StatusVal> StatusList()
{
List<StatusVal> returnVal = new List<StatusVal>();
StatusType sv = new StatusType();
returnVal.Add(sv.Pending);
returnVal.Add(sv.Authorised);
returnVal.Add(sv.Rejected);
returnVal.Add(sv.Sent);
returnVal.Add(sv.InActive);
return returnVal;
}
}
public class StatusVal
{
public StatusVal(int a, string b)
{
this.ID = a;
this.Name = b;
}
public int ID { get; set; }
public string Name { get; set; }
}
I then get the list of StatusVal with the following webmethod:
[WebMethod]
public List<ATBusiness.StatusVal> GetStatus()
{
ATBusiness.StatusType a = new ATBusiness.StatusType();
return a.StatusList();
}
I cannot however use this webmethod as referring it, I get the error: StatusVal cannot be serialized because it does not have a parameterless constructor.
I don't quite understand: should I pass params into the StatusValue type defined as the WebMethod's return Type?
I need this to return a list of StatusVals as per the StatusList() method.
As the error says, your class needs a constructor without parameters. When unserializing, the runtime will use that constructor instead of the one you have defined.
Something like:
public StatusVal()
{
}
When you created a constructor with parameters, you are automatically removing the default no-parameter constructor, and that's what the compiler is complaining about.