c# - Deserialization of IOrderedEnumerable<T> with JSON.NET -


my team , came accross weird behavior json.net deserialization in c#.

we have simple 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);     } } 

then, want post/put viewmodel in api controller

[httppost] public ihttpactionresult post([frombody]testclass test) {     return ok(test); } 

calling api json :

{     name: "tiit",     "ordereddatas": [         2,         3,         4     ], } 

with call, saw constructor wasn't called (which can explained fact it's not default constructor). strange thing if change type of collection ienumerable or ilist, constructor called.

if change constructor of testclass default 1 :

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

the object retrieve controller not null. , if change type of collection ienumerable , keep constructor parameter (public testclass(string name)), it'll work also.

another strange thing object test in controller "null". not ordereddatas null, entire object.

if add attribute [jsonobject] on class, , [jsonignore] on property ordereddata, works.

for now, changed object simple list , it's working fine, wondering why json deserialization act different depending on type of collection.

if use directly jsonconvert.deserialize :

var json = "{ name: 'tiit', 'ordereddatas': [2,3,4,332232] }"; var result = jsonconvert.deserializeobject<testclass>(json); 

we can saw actual exception :

cannot create , populate list type system.linq.iorderedenumerable`1[system.int64]. path 'ordereddatas', line 1, position 33.

any idea / appreciated.

thanks !

** edit : answers. there 1 thing keep finding weird (i put in bold), if have idea explain behavior, please tell me **

as kisu states in this answer, json.net fails deserilize testclass because has no built-in logic mapping iorderedenumerable<t> interface concrete class required deserialize it. not surprising, because:

  1. iorderedenumerable<telement> has no publicly available property indicating how sorted -- ascending; descending; using complex keyselector delegate refers 1 or more captured variables. information lost during serialization - , serializing delegate such keyselector delegate anyway isn't implemented if information public.

  2. the concrete .net class implements interface, orderedenumerable<telement, tkey>, internal. returned enumerable.orderby() or enumerable.thenby() not created directly in application code. see here sample implementation.

a minimal change testclass make serializable json.net add params long [] ordereddatas constructor:

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

this takes advantage of fact that, when type has 1 public constructor, if constructor parameterized, json.net invoke construct instances of type, matching , deserializing json properties constructor arguments name (modulo case).

that being said, don't recommend design. reference source orderedenumerable<telement, tkey>.getenumerator() can see underlying enumerable re-sorted each time getenumerator() called. implementation quite inefficient. , of course ordering logic lost after round-tripping. see mean, consider following:

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

the first call console.writeline() prints

{   "ordereddatas": [     1,     6   ],   "name": "tiit" } 

and second prints

{   "ordereddatas": [     6,     1   ],   "name": "tiit" } 

as can see, ordereddatas re-sorted every time enumerated, according current value of captured variable factor. json.net snapshot current sequence when serializing, has no way serialize dynamic logic of how sequence re-sorts itself.

sample fiddle.

of course, when change property ilist<long> no exception thrown , object deserialized. json.net has built-in logic deserialize interfaces ilist<t> , ienumerable<t> list<t>. has no built-in concrete type use iorderedenumerable<t> reasons explained.

update

you ask, paraphrase, why parameterized constructor not called when trying , failing deserialize nested iorderedenumerable<t> property, while parameterless constructor called?

json.net uses different order of operations when deserializing objects , without parameterized constructor. difference explained in answer question usage of non-default constructor breaks order of deserialization in json.net. bearing answer in mind, when json.net throw exception trying , failing deserialize instance of type iorderedenumerable<t>?

  1. when type has parameterized constructor, json.net chew through properties in json file , deserialize values each 1 before constructing object, pass appropriate values constructor. when exception gets thrown object not constructed.

  2. when type has parameterless constructor, json.net construct instance , begin chew through json properties deserialize them, throwing exception partway through. when exception gets thrown object constructed not deserialized.

apparently, somewhere in framework exception json.net getting caught , swallowed. in case #1 null object , in case #2 partially deserialized object.


Comments

Popular posts from this blog

java - Jasper subreport showing only one entry from the JSON data source when embedded in the Title band -

serialization - Convert Any type in scala to Array[Byte] and back -

SonarQube Plugin for Jenkins does not find SonarQube Scanner executable -