I was working on a generic class, something like this in fact:
public class SweepDriver<InputType, OutputType> where InputType : class where OutputType : ICalcOutput1
{
ICalculator<InputType, OutputType> _calculator;
public SweepDriver(ICalculator<InputType, OutputType>, calculator)
{
_calculator = calculator;
}
public void runSweep(JObject input, IBindingSweepOutput1<OutputType> outputModel)
{
IEnumerable<string> sweepConditions = SweepConditionGenerator.generateSweepConditions(input);
foreach (var run in sweepConditions)
{
//Throws error InputType inputObject = JSonConvert.DeserializeObject<InputType>(run)
//Perform calculations
var output = _calculator.calculate(inputObject);
outputModel.AddOutput(output);
}
}
}
You can see that I hit a bit of a snag. I get an error when trying to deserialize my Json.
It would just be so simple if I could deserialize to my interface, but it turns out I need a concrete class which makes some sense.
But once you start working with nested interfaces it get's a bit painful to create concrete classes. Once you get multiple levels of nested interfaces it becomes quite tedious to wire up for Newtonsoft to deserialize (There's a long discussion on StackOverflow about how to do it):
public interface basicInterface
{
int firstField { get; set; }
string secondField { get; }
}
public interface interfaceWithInheritance : basicInterface
{
int thirdField { get; }
}
public interface composedInterface : interfaceWithInheritance
{
basicInterface composedField { get; }
}
In my case, I wanted to spare my colleagues the tedium of defining the interface and a concrete class which is essentially the same (DRY Principle). While Visual Studio can generate simple implementations, it can't handle these nested structures well at all.
Even if they did define the concrete class, I would have to mess up my elegant generic implementation above to allow the concrete type to be passed as well as the base interface Uggghh!
What if I could use duck typing to make a JObject
behave like my composedInterface
?
After several failed attempts I decided to create a proxy class using Castle.DynamicProxy and have requests to my properties redirected to an underlying JObject
. The final result is an extension method for JObject
which you can see below. In my use case, it is very performant. It uses late binding so it won't waste cpu cycles on properties that never get accessed.
using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;
namespace LL.Utilities.Std.Json
{
public static class JObjectExtension
{
private static ProxyGenerator _generator = new ProxyGenerator();
public static dynamic toProxy(this JObject targetObject, Type interfaceType)
{
return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
}
public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
{
return toProxy(targetObject, typeof(InterfaceType));
}
}
[Serializable]
public class JObjectInterceptor : IInterceptor
{
private JObject _target;
public JObjectInterceptor(JObject target)
{
_target = target;
}
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
{
var returnType = invocation.Method.ReturnType;
methodName = methodName.Substring(4);
if (_target == null || _target[methodName] == null)
{
if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
{
invocation.ReturnValue = null;
return;
}
}
if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
{
invocation.ReturnValue = _target[methodName].ToObject(returnType);
}
else
{
invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
}
}
else
{
throw new NotImplementedException("Only get accessors are implemented in proxy");
}
}
}
}
It's written against dotnetstandard 1.4 so it works in .net core and .net framework too. So to go back to my original example, we can change the code as follows to achieve ultimate hapiness ;-).
//Replace
InputType inputObject = JSonConvert.DeserializeObject<InputType>(run)
//With
var objRun = JObject.Parse(run);
InputType inputObject = objRun.toProxy<InputType>();
EDIT:
Since writing this post, I have expanded the code to support more and more use cases, I have published the result at https://github.com/sudsy/JsonDuckTyper please feel free to enhance it further if needed.