使用JSON.NET反编译IOrderedEnumerable <T>

Cyr*_*uis 7 c# serialization json json.net json-deserialization

我和我的团队在C#中使用JSON.NET反序列化来解决这个奇怪的行为.

我们有一个简单的ViewModel IOrderedEnumerable<long>:

public class TestClass
{
    public IOrderedEnumerable<long> orderedDatas { get; set; }
    public string Name { get; set; }

    public TestClass(string name)
    {
        this.Name = name;
        this.orderedDatas = new List<long>().OrderBy(p => p);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我们只想在API控制器中POST/PUT这个viewmodel

[HttpPost]
public IHttpActionResult Post([FromBody]TestClass test)
{
    return Ok(test);
}
Run Code Online (Sandbox Code Playgroud)

使用看起来像这样的json调用此API:

{
    Name: "tiit",
    "orderedDatas": [
        2,
        3,
        4
    ],
}
Run Code Online (Sandbox Code Playgroud)

通过这个调用,我们看到没有调用构造函数(可以解释为它不是默认构造函数).但奇怪的是,如果我们将集合的类型更改为IEnumerableIList,则正确调用构造函数.

如果我们将构造函数更改TestClass为默认构造函数:

public class TestClass
{
    public IOrderedEnumerable<long> orderedDatas { get; set; }
    public string Name { get; set; }

    public TestClass()
    {
        this.Name = "default";
        this.orderedDatas = new List<long>().OrderBy(i => i);
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器检索的对象不为null.如果我们将集合的类型更改为IEnumerable并且我们将构造函数保留为参数(public TestClass(string name)),它也会起作用.

另一个奇怪的事情是控制器中的对象测试是"空".orderedDatas不仅是null,而且是整个对象.

如果我们[JsonObject]在类上添加一个属性,并[JsonIgnore]在属性orderedData 上添加一个属性,它就可以工作.

现在,我们将对象更改为一个简单的List并且它工作正常,但我们想知道为什么JSON反序列化根据集合的类型而有所不同.

如果我们直接使用JsonConvert.Deserialize:

var json = "{ Name: 'tiit', 'orderedDatas': [2,3,4,332232] }";
var result = JsonConvert.DeserializeObject<TestClass>(json);
Run Code Online (Sandbox Code Playgroud)

我们可以看到实际的异常:

无法创建和填充列表类型System.Linq.IOrderedEnumerable`1 [System.Int64].路径'orderedDatas',第1行,第33位.

任何想法/帮助表示赞赏.

谢谢 !

**编辑:谢谢你的回答.有一件事我一直发现很奇怪(我用粗体表示),如果你有任何想法解释这种行为,请告诉我**

dbc*_*dbc 6

正如kisu在这个答案中指出的那样,Json.NET无法对你进行deserilize,TestClass因为它没有用于将IOrderedEnumerable<T>接口映射到具体类的内置逻辑,因为它需要对其进行反序列化.这并不奇怪,因为:

  1. IOrderedEnumerable<TElement>没有公开的财产,表明它是如何排序的 - 升序; 降; 使用一些keySelector引用一个或多个捕获变量的复杂委托.因此,在序列化期间这些信息将丢失 - 并且keySelector即使信息是公开的,也无法实现序列代理(例如委托)的序列化.

  2. 实现此接口的具体.Net类OrderedEnumerable<TElement, TKey>internal.通常它是由应用程序代码直接返回Enumerable.OrderBy()Enumerable.ThenBy()不直接创建的.请参阅此处以获取示例实现.

一个最小的对你的变化TestClass,使其由Json.NET序列化将是添加params long [] orderedDatas到您的构造函数:

public class TestClass
{
    public IOrderedEnumerable<long> orderedDatas { get; set; }
    public string Name { get; set; }

    public TestClass(string name, params long [] orderedDatas)
    {
        this.Name = name;
        this.orderedDatas = orderedDatas.OrderBy(i => i);
    }
}
Run Code Online (Sandbox Code Playgroud)

这利用了以下事实:当一个类型只有一个公共构造函数时,如果该构造函数被参数化,Json.NET将调用它来构造该类型的实例,通过名称匹配和反序列化JSON属性到构造函数参数(模数情况).

话虽这么说,我不推荐这种设计.从参考源OrderedEnumerable<TElement, TKey>.GetEnumerator()我们可以看到,每次GetEnumerator()调用底层枚举都会重新排序.因此,您的实施可能效率很低.当然,在往返之后,订购逻辑将会丢失.要了解我的意思,请考虑以下事项:

var test = new TestClass("tiit");
int factor = 1;
test.orderedDatas = new[] { 1L, 6L }.OrderBy(i => factor * i);
Console.WriteLine(JsonConvert.SerializeObject(test, Formatting.Indented));
factor = -1;
Console.WriteLine(JsonConvert.SerializeObject(test, Formatting.Indented));
Run Code Online (Sandbox Code Playgroud)

第一次Console.WriteLine()打印打印

{
  "orderedDatas": [
    1,
    6
  ],
  "Name": "tiit"
}
Run Code Online (Sandbox Code Playgroud)

第二次打印

{
  "orderedDatas": [
    6,
    1
  ],
  "Name": "tiit"
}
Run Code Online (Sandbox Code Playgroud)

如您所见,orderedDatas根据捕获变量的当前值,每次枚举时都会重新排序factor.Json.NET所能做的就是在序列化时对当前序列进行快照,它无法序列化序列如何不断重新排序自己的动态逻辑.

样品小提琴.

当然,当您更改属性时,IList<long>不会抛出任何异常,并且对象将被反序列化.Json.NET具有内置逻辑来反序列化接口IList<T>IEnumerable<T>as List<T>.IOrderedEnumerable<T>由于所解释的原因,它没有内置的混凝土类型.

更新

你要问一下,为什么参数化构造函数在尝试和未能反序列化嵌套IOrderedEnumerable<T>属性时没有被调用,而无参数构造函数被调用?

在使用和不使用参数化构造函数反序列化对象时,Json.NET使用不同的操作顺序.在Json.net中使用非默认构造函数中断反序列化的问题的答案中解释了这种差异.考虑到这一点,Json.NET究竟何时会抛出异常,尝试并且无法反序列化类型的实例IOrderedEnumerable<T>

  1. 当类型具有参数化构造函数时,Json.NET将构造对象之前咀嚼JSON文件中的属性并对每个属性进行反序列化,然后将适当的值传递给构造函数.因此,当抛出异常时,不构造对象.

  2. 当类型具有无参数构造函数时,Json.NET将构造一个实例并开始咀嚼JSON属性以反序列化它们,并在中途抛出异常.因此,当抛出异常时,对象被构造但未完全反序列化.

显然,在您的框架中的某个地方,Json.NET的异常被捕获并被吞没.因此,在#1的情况下,您将获得一个null对象,如果是#2,您将获得一个部分反序列化的对象.