Json.Net DeserializeObject failing with OData.Delta - integers only Json.Net DeserializeObject failing with OData.Delta - integers only asp.net asp.net

Json.Net DeserializeObject failing with OData.Delta - integers only


OData.Delta<T> does not work with Json.Net for any number Types other than Int64. The easiest approach is to write a replacement for OData.Delta<T> (which I've done on company time so I can't post it in its entirety sorry) containing methods like this:

private bool TrySetInt32(object value, PropertyInfo propertyInfo, bool isNullable){    var done = false;    if (value is Int32)    {        propertyInfo.SetValue(_obj, value);        done = true;    }    else if (value == null)    {        if (isNullable)        {            propertyInfo.SetValue(_obj, value);            done = true;        }    }    else if (value is Int64) //Json.Net - fallback for numbers is an Int64    {        var val = (Int64)value;        if (val <= Int32.MaxValue && val >= Int32.MinValue)        {            done = true;            propertyInfo.SetValue(_obj, Convert.ToInt32(val));        }    }    else    {        Int32 val;        done = Int32.TryParse(value.ToString(), out val);        if (done)            propertyInfo.SetValue(_obj, val);    }    return done;}

The class can be a dynamic generic like this:

public sealed class Patchable<T> : DynamicObject where T : class, new()

With a working variable like this:

T _obj = new T();

In the overridden TrySetMember method, we need to check the underlying type of the property using reflection and call the appropriate TrySet... method like this:

if (underlyingType == typeof(Int16))    done = TrySetInt16(value, propertyInfo, isNullable);else if (underlyingType == typeof(Int32))    done = TrySetInt32(value, propertyInfo, isNullable);

If the value is set successfully we can add the property name to a list that we can then use for patching the original record like this:

if (done)    _changedPropertyNames.Add(propertyInfo.Name);public void Patch(T objectToPatch){    foreach (var propertyName in _changedPropertyNames)    {        var propertyInfo = _obj.GetType().GetProperty(propertyName);        propertyInfo.SetValue(objectToPatch, propertyInfo.GetValue(_obj));    }}

68 unit tests later, it all seems to work pretty well. Here's an example:

class TestObjWithInt32{    public Int32 Int32 { get; set; }    public Int32? SetNullable { get; set; }    public Int32? UnsetNullable { get; set; }}[TestMethod]public void IsApplied_When_Int32IsDeserializedToPatchable(){    string testData = "{\"Int32\":1,\"SetNullable\":1}";    var deserializedPatchable = JsonConvert.DeserializeObject<Patchable<TestObjWithInt32>>(testData);    var result = deserializedPatchable.ChangedPropertyNames.Contains("Int32");    Assert.IsTrue(result);    var patchedObject = new TestObjWithInt32();    Assert.AreEqual<Int32>(0, patchedObject.Int32);    deserializedPatchable.Patch(patchedObject);    Assert.AreEqual<Int32>(1, patchedObject.Int32);    Assert.IsNull(patchedObject.UnsetNullable);    Assert.IsNotNull(patchedObject.SetNullable);}


This is my implementation for this issue based on Rob solution:

public sealed class Patchable<T> : DynamicObject where T : class {    private readonly IDictionary<PropertyInfo, object> changedProperties = new Dictionary<PropertyInfo, object>();    public override bool TrySetMember(SetMemberBinder binder, object value) {        var pro = typeof (T).GetProperty(binder.Name);        if (pro != null)            changedProperties.Add(pro, value);        return base.TrySetMember(binder, value);    }    public void Patch(T delta) {        foreach (var t in changedProperties)            t.Key.SetValue(                delta,                t.Key.PropertyType.IsEnum ? Enum.Parse(t.Key.PropertyType, t.Value.ToString()) : Convert.ChangeType(t.Value, t.Key.PropertyType));    }}

I removed the requisite of an empty constructor in generic type parameter using the dictionary instead of a temporal object.

Thanks Rob ;)