Json.Net DeserializeObject失败,出现OData.Delta-仅限整数 [英] Json.Net DeserializeObject failing with OData.Delta - integers only

查看:64
本文介绍了Json.Net DeserializeObject失败,出现OData.Delta-仅限整数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题正在影响我的ASP.Net WebApi修补程序方法,该方法看起来非常像这样:

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

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

但这不是WebApi的问题-故障出在Json.Net和OData.Delta之间.

But it's not WebApi that's the problem - the failure is between Json.Net and OData.Delta.

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

The problem is JsonConvert.DeserializeObject does not see integers of OData.Delta objects and I'm wondering if there's a workaround or fix I can apply.

更新:在Json.Net库中已经编写了代码(请参见右下方),可以解决此问题.只需要将它包含在下一个更新中(如果James Newton-King允许)

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

单元测试以证明问题存在(为清楚起见,使用下面的语句)

Unit tests to prove the problem exists (using statements moved below for clarity)

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

Test 1: Fails with an 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);
}

测试2:成功使用长整数(Int64)

Test 2: Succeeds with a long (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);
}

并且为了确保反序列化从一开始就起作用,这两个测试都通过了.

And just to be sure that deserialization works to begin with, these two tests both pass.

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

我发现了 OData错误报告,听起来像是一个类似的问题,但是它已经过时并且已经关闭,因此可能不是.

I found this OData bug report which sounds like a similar issue but its old and closed so probably not.

任何帮助都会很棒.

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

Using statements (from the top of the test file):

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行:

Solution if accepted by James Newton-King - change to release 6.0.6. Replace JsonSerializerInternalReader.cs line 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;
    }
}

推荐答案

OData.Delta< T>对于Int64以外的任何其他数字类型,均不能与Json.Net一起使用.最简单的方法是编写OData.Delta T的替换. (这是我在公司时间所做的,因此无法完整地发布),包含以下方法:

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()

具有这样的工作变量:

T _obj = new T();

在覆盖的TrySetMember方法中,我们需要使用反射检查属性的基础类型,并调用适当的TrySet ...方法,如下所示:

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个单元测试之后,一切似乎都运行良好.这是一个示例:

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

这篇关于Json.Net DeserializeObject失败,出现OData.Delta-仅限整数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆