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

前端 未结 2 1035
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-31 19:25

This problem is affecting my ASP.Net WebApi Patch method which looks a lot like this:

public MyModel Patch(int id, [FromBody]Delta newRecord){         


        
2条回答
  •  星月不相逢
    2020-12-31 20:04

    OData.Delta does not work with Json.Net for any number Types other than Int64. The easiest approach is to write a replacement for OData.Delta (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 : 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>(testData);
        var result = deserializedPatchable.ChangedPropertyNames.Contains("Int32");
        Assert.IsTrue(result);
        var patchedObject = new TestObjWithInt32();
        Assert.AreEqual(0, patchedObject.Int32);
        deserializedPatchable.Patch(patchedObject);
        Assert.AreEqual(1, patchedObject.Int32);
        Assert.IsNull(patchedObject.UnsetNullable);
        Assert.IsNotNull(patchedObject.SetNullable);
    }
    

提交回复
热议问题