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:
iorderedenumerable<telement>
has no publicly available property indicating how sorted -- ascending; descending; using complexkeyselector
delegate refers 1 or more captured variables. information lost during serialization - , serializing delegate suchkeyselector
delegate anyway isn't implemented if information public.the concrete .net class implements interface,
orderedenumerable<telement, tkey>
,internal
. returnedenumerable.orderby()
orenumerable.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>
?
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.
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
Post a Comment