2014-11-21 39 views
7

此問題影響我的ASP.Net的WebAPI修補方法,它看起來很像是:Json.Net DeserializeObject與OData.Delta失敗 - 只能爲整數

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

但它不是的WebAPI這就是問題所在 - 失敗介於Json.Net和OData.Delta之間。

問題是JsonConvert.DeserializeObject看不到OData.Delta對象的整數,我想知道是否有我可以應用的解決方法或修復。

更新:在Json.Net庫中已經編寫了代碼(參見下面的右邊)來解決這個問題。只需要它被包括在下一次更新(如果詹姆斯牛頓 - 金允許它)

更新2:經過進一步測試,我決定最好的行動方針是停止使用OData.Delta和寫我自己的(見答案)

單元測試來證明問題的存在(使用下面的語句移動爲清楚起見)

測試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<TestObjWithInt>>(testData); 
    var result = deserializedDelta.GetChangedPropertyNames().Contains("Int"); 
    Assert.IsTrue(result); 
} 

特ST 2:用長(Int64的)成功

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

而只是爲了確保反序列化工作開始說起,這兩個測試都通過。

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

我發現this OData的bug報告這聽起來像一個類似的問題,但它的老所以大概不會關閉。

任何幫助將是偉大的。

using語句(從測試文件的頂部):

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

如果接受詹姆斯·牛頓王解決方案 - 改變釋放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.MaxValue && ((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; 
    } 
} 
+0

在情況下,它可以幫助任何人,有一個非常類似的問題,我已經有了一個更簡單,更「黑客」修復線程。 [這是答案。](http://stackoverflow.com/a/41749206/2283050) – 2017-01-19 19:14:51

回答

3

OData.Delta <牛逼>不Json.Net工作比其他的Int64任意數量的類型。最簡單的方法是編寫OData的替代品。三角洲<牛逼>(我已經在公司的時間內完成,所以我不能將它張貼在其整體對不起)含有的方法是這樣的:

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

類可以是動態一般是這樣的:

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

有了這樣的工作變量:

T _obj = new T(); 

在重寫TrySetMember方法,我們需要使用查詢的基礎類型的屬性的反射並調用相應的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單元測試後,這一切似乎工作得很好。這裏有一個例子:

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

這是我實現了基於羅布解決這個問題:

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

我使用字典而不是暫時物件的去除泛型類型參數的空構造的必要。

感謝羅布;)

+0

很好的嘗試代碼最小化,但不幸的是它失敗了17個單元測試,包括我的答案中的一個。問題是,而不是Json.Net壓制InvalidCastException,它已被移入Patch方法。我的答案(雖然導致長課)儘可能避免例外。 – Rob 2014-11-25 23:24:57