我们有一个简单的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 ],}
通过这个调用,我们看到没有调用构造函数(可以解释为它不是默认构造函数).但奇怪的是,如果我们将集合的类型更改为IEnumerable或IList,则正确调用构造函数.
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); } }
控制器检索的对象不为null.
如果我们将集合的类型更改为IEnumerable并且我们使用参数(public TestClass(string name))保留构造函数,那么它也会起作用.
另一个奇怪的事情是控制器中的对象测试是“空”. orderedDatas不仅是null,而且是整个对象.
如果我们在类上添加一个属性[JsonObject],并在属性orderedData上添加一个[JsonIgnore],它就可以了.
现在,我们将对象更改为一个简单的List并且它工作正常,但我们想知道为什么JSON反序列化根据集合的类型而有所不同.
如果我们直接使用JsonConvert.Deserialize:
var json = "{ Name: 'tiit','orderedDatas': [2,4,332232] }"; var result = JsonConvert.DeserializeObject<TestClass>(json);
我们可以看到实际的异常:
Cannot create and populate list type
System.Linq.IOrderedEnumerable`1[System.Int64]. Path ‘orderedDatas’,
line 1,position 33.
任何想法/帮助表示赞赏.
谢谢 !
**编辑:谢谢你的回答.有一件事我一直发现很奇怪(我用粗体表示),如果你有任何想法解释这种行为,请告诉我**
解决方法
> IOrderedEnumerable<TElement>
没有公开的财产,表明它是如何排序的 – 升序;降;使用引用一个或多个captured variables的复杂keySelector
委托.因此,在序列化期间此信息将丢失 – 并且即使信息是公共的,也无法实现序列化诸如keySelector委托之类的委托.
>实现此接口的具体.Net类OrderedEnumerable<TElement,TKey>
是内部的.通常它由Enumerable.OrderBy()或Enumerable.ThenBy()返回,而不是直接在应用程序代码中创建.有关示例实现,请参见here.
对您的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); } }
这利用了以下事实:当一个类型只有一个公共构造函数时,如果该构造函数被参数化,Json.NET将调用它来构造该类型的实例,通过名称匹配和反序列化JSON属性到构造函数参数(模数情况) .
话虽这么说,我不推荐这种设计.从调用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时都会重新排序. Json.NET所能做的就是在序列化时对当前序列进行快照,它无法序列化序列如何不断重新排序自己的动态逻辑.
样本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>?
>当类型具有参数化构造函数时,Json.NET将在构造对象之前咀嚼JSON文件中的属性并对每个属性进行反序列化,然后将适当的值传递给构造函数.因此,当抛出异常时,不构造对象.
>当类型具有无参数构造函数时,Json.NET将构造一个实例并开始咀嚼JSON属性以反序列化它们,并在中途抛出异常.因此,对象被构造但未完全反序列化.
显然,在您的框架中的某个地方,Json.NET的异常被捕获并被吞没.因此,在#1的情况下,您将获得一个null对象,如果是#2,您将获得一个部分反序列化的对象.