使用JSON.NET

时间:2016-09-07 12:51:23

标签: c# json serialization 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);
    }
}

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

[HttpPost]
public IHttpActionResult Post([FromBody]TestClass test)
{
    return Ok(test);
}

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

{
    Name: "tiit",
    "orderedDatas": [
        2,
        3,
        4
    ],
}

通过此调用,我们看到构造函数未被调用(可以通过它不是默认构造函数的事实来解释)。但奇怪的是,如果我们将集合的类型更改为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);
    }
}

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

另一个奇怪的事情是控制器中的对象测试是&#34; null&#34;。 orderedDatas不仅是null,而且是整个对象。

如果我们在类上添加属性[JsonObject],并在属性orderedData上添加[JsonIgnore],则可以正常工作。

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

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

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

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

  

无法创建和填充列表类型   System.Linq.IOrderedEnumerable`1 [System.Int64]。 Path&#39; orderedDatas&#39;,   第1行,第33位。

感谢任何想法/帮助。

谢谢!

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

2 个答案:

答案 0 :(得分:6)

正如kisu在this answer中陈述的那样,Json.NET无法对您的TestClass进行deserilize,因为它没有用于将IOrderedEnumerable<T>接口映射到具体类的内置逻辑。反序列化它。这并不奇怪,因为:

  1. IOrderedEnumerable<TElement>没有公开的属性,表明它是如何排序的 - 升序;降;使用引用一个或多个keySelector的复杂captured variables委托。因此,在序列化过程中这些信息会丢失 - 即使信息是公开的,也无法序列化keySelector代表等代表。

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

  3. TestClass进行 minimal 更改以使其可由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);
        }
    }
    

    这利用了以下事实:当一个类型只有一个公共构造函数时,如果该构造函数被参数化,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));
    

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

    {
      "orderedDatas": [
        1,
        6
      ],
      "Name": "tiit"
    }
    

    第二次打印

    {
      "orderedDatas": [
        6,
        1
      ],
      "Name": "tiit"
    }
    

    如您所见,根据捕获的变量orderedDatas的当前值,每次枚举时factor都会重新排序。 Json.NET可以做的就是在序列化时快照当前序列,它无法序列化动态逻辑序列如何不断重新排序。< / p>

    示例fiddle

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

    <强>更新

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

    当使用和不使用参数化构造函数反序列化对象时,Json.NET使用不同的操作顺序。在Usage of non-default constructor breaks order of deserialization in Json.net问题的答案中解释了这种差异。考虑到这一点,Json.NET究竟何时抛出异常,尝试并且无法反序列化IOrderedEnumerable<T>类型的实例?

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

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

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

答案 1 :(得分:3)

从我在JsonArrayContract的构造函数中看到的,接口IOrderedEnumerable<>未正确处理,很可能是因为List<>T[]没有实现它,因此需要一些更特殊的处理,基本上可以被其他一些界面取代。

此外,在反序列化的类中放置无大小的接口是没有意义的,因为根据定义,它们不能是无限的,可以被具有有限的已知大小的集合替换。

请记住,在反序列化对象中使用接口会涉及来自反序列化程序的一些“猜测工作”,以创建正确的实例。

如果您想要一些具有指定顺序的不可变集合,我建议您使用IReadOnlyList<>