Json.Net DeserializeObject失败,只有OData.Delta – 整数

这个问题正在影响我的ASP.Net WebApi补丁方法,看起来很像这样:

public MyModel Patch(int id, [FromBody]Delta newRecord){/*stuff here*/} 

但这不是WebApi的问题 – 失败发生在Json.Net和OData.Delta之间。

问题是JsonConvert.DeserializeObject没有看到OData.Delta对象的整数,我想知道是否有可以应用的解决方法或修复。

更新:在Json.Net库中编写了代码(请参阅下面的右下角),以解决此问题。 只需将它包含在下一次更新中(如果James Newton-King允许的话)

更新2:经过进一步测试,我决定最好的做法是停止使用OData.Delta并自己编写(见答案)

用于certificate问题存在的unit testing(为了清楚起见,使用下面的语句)

测试1:使用int(Int32)失败:

 class TestObjWithInt { public int Int { get; set; } } [TestMethod] public void IsApplied_When_IntIsDeserializedToDelta() { string testData = "{\"Int\":1}"; var deserializedDelta = JsonConvert.DeserializeObject<Delta>(testData); var result = deserializedDelta.GetChangedPropertyNames().Contains("Int"); Assert.IsTrue(result); } 

测试2:长成功(Int64)

 class TestObjWithLong { public long Long { get; set; } } [TestMethod] public void IsApplied_When_LongIsDeserializedToDelta() { string testData = "{\"Long\":1}"; var deserializedDelta = JsonConvert.DeserializeObject<Delta>(testData); var result = deserializedDelta.GetChangedPropertyNames().Contains("Long"); Assert.IsTrue(result); } 

并且为了确保反序列化工作开始,这两个测试都通过了。

 [TestMethod] public void IsApplied_When_LongIsDeserializedToTestObject() { string testData = "{\"Long\":1}"; var deserializedObject = JsonConvert.DeserializeObject(testData); var result = deserializedObject.Long == 1; Assert.IsTrue(result); } [TestMethod] public void IsApplied_When_IntIsDeserializedToTestObject() { string testData = "{\"Int\":1}"; var deserializedObject = JsonConvert.DeserializeObject(testData); var result = deserializedObject.Int == 1; Assert.IsTrue(result); } 

我发现这个 OData错误报告听起来像一个类似的问题,但它的旧和封闭所以可能不是。

任何帮助都会很棒。

使用语句(从测试文件的顶部):

 using System; using System.Linq; using System.Web.Http.OData; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; 

解决方案如果被James Newton-King接受 – 更改为6.0.6版。 替换JsonSerializerInternalReader.cs第1581行:

 contract.TrySetMember(newObject, memberName, value); 

有:

 bool done = false; while (!(done = done || contract.TrySetMember(newObject, memberName, value))) { switch (reader.TokenType) { case JsonToken.Integer: if (value is long && ((long)value) = Int32.MinValue) value = Convert.ToInt32(value); //Add else if (...) to cast to other data types here (none additional required to date). else done = true; break; default: done = true; break; } } 

对于除Int64之外的任何数字类型,OData.Delta 不适用于Json.Net。 最简单的方法是为OData.Delta 写一个替代品(我在公司时间完成了所以我不能完整地发布它)包含这样的方法:

 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; } 

该类可以是动态generics,如下所示:

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

使用这样的工作变量:

 T _obj = new T(); 

在重写的TrySetMember方法中,我们需要使用reflection检查属性的基础类型,并调用相应的TrySet …方法,如下所示:

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

如果成功设置了值,我们可以将属性名称添加到列表中,然后我们可以使用这些列表修补原始记录,如下所示:

 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 testing,一切似乎都运行良好。 这是一个例子:

 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); } 

这是我基于Rob解决方案的这个问题的实现:

 public sealed class Patchable : DynamicObject where T : class { private readonly IDictionary changedProperties = new Dictionary(); 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)); } } 

我使用字典而不是时间对象删除了generics类型参数中的空构造函数的必需项。

谢谢Rob;)