json - JavaScriptSerializer and ASP.Net MVC model binding yield different results -



json - JavaScriptSerializer and ASP.Net MVC model binding yield different results -

i'm seeing json deserialization problem can not explain or fix.

code public class model { public list<itemmodel> items { get; set; } } public class itemmodel { public int sid { get; set; } public string name { get; set; } public datamodel info { get; set; } public list<itemmodel> items { get; set; } } public class datamodel { public double? d1 { get; set; } public double? d2 { get; set; } public double? d3 { get; set; } } public actionresult save(int id, model model) { } data

{'items':[{'sid':3157,'name':'a name','items':[{'sid':3158,'name':'child name','data':{'d1':2,'d2':null,'d3':2}}]}]}

unit test - passing var jss = new javascriptserializer(); var m = jss.deserialize<model>(json); assert.equal(2, m.items.first().items.first().data.d1); the problem

the same json string, when sent save action, doesn't deserialized same way, specially d1, d2, , d3 values set null. always.

what's going on here, , how can prepare it?

it might sound counter-intuitive, should send doubles strings in json:

'data':{'d1':'2','d2':null,'d3':'2'}

here finish test code invokes controller action using ajax, , allows binding every value of model:

$.ajax({ url: '@url.action("save", new { id = 123 })', type: 'post', contenttype: 'application/json', data: json.stringify({ items: [ { sid: 3157, name: 'a name', items: [ { sid: 3158, name: 'child name', data: { d1: "2", d2: null, d3: "2" } } ] } ] }), success: function (result) { // ... } });

and illustrate extent of problem of trying deserialize numeric types json, let's take few examples:

public double? foo { get; set; } { foo: 2 } => foo = null { foo: 2.0 } => foo = null { foo: 2.5 } => foo = null { foo: '2.5' } => foo = 2.5 public float? foo { get; set; } { foo: 2 } => foo = null { foo: 2.0 } => foo = null { foo: 2.5 } => foo = null { foo: '2.5' } => foo = 2.5 public decimal? foo { get; set; } { foo: 2 } => foo = null { foo: 2.0 } => foo = null { foo: 2.5 } => foo = 2.5 { foo: '2.5' } => foo = 2.5

now let's same non-nullable types:

public double foo { get; set; } { foo: 2 } => foo = 2.0 { foo: 2.0 } => foo = 2.0 { foo: 2.5 } => foo = 2.5 { foo: '2.5' } => foo = 2.5 public float foo { get; set; } { foo: 2 } => foo = 2.0 { foo: 2.0 } => foo = 2.0 { foo: 2.5 } => foo = 2.5 { foo: '2.5' } => foo = 2.5 public decimal foo { get; set; } { foo: 2 } => foo = 0 { foo: 2.0 } => foo = 0 { foo: 2.5 } => foo = 2.5 { foo: '2.5' } => foo = 2.5

conclusion: deserializing numeric types json 1 big hell-of-a mess. utilize strings in json. , of course, when utilize strings, careful decimal separator civilization dependent.

i have been asked in comments section why passes unit tests, doesn't work in asp.net mvc. reply simple: it's because asp.net mvc many more things simple phone call javascriptserializer.deserialize, unit test does. comparing apples oranges.

let's dive deeper happens. in asp.net mvc 3 there's built-in jsonvalueproviderfactory internally uses javascriptdeserializer class deserialize json. works, have seen, in unit test. there's much more in asp.net mvc, uses default model binder responsible instantiating action parameters.

and if @ source code of asp.net mvc 3, , more defaultmodelbinder.cs class, notice next method invoked each property have value set:

public class defaultmodelbinder : imodelbinder { ............... [suppressmessage("microsoft.globalization", "ca1304:specifycultureinfo", messageid = "system.web.mvc.valueproviderresult.convertto(system.type)", justification = "the target object should create right civilization determination, not method.")] [suppressmessage("microsoft.design", "ca1031:donotcatchgeneralexceptiontypes", justification = "we're recording exception can deed on later.")] private static object convertproviderresult(modelstatedictionary modelstate, string modelstatekey, valueproviderresult valueproviderresult, type destinationtype) { seek { object convertedvalue = valueproviderresult.convertto(destinationtype); homecoming convertedvalue; } grab (exception ex) { modelstate.addmodelerror(modelstatekey, ex); homecoming null; } } ............... }

let's focus more on next line:

object convertedvalue = valueproviderresult.convertto(destinationtype);

if suppose had property of type nullable<double>, here's when debug application:

destinationtype = typeof(double?);

no surprises here. our destination type double? because that's used in our view model.

then take @ valueproviderresult:

see rawvalue property out there? can guess type?

so method throws exception because cannot convert decimal value of 2.5 double?.

do notice value returned in case? that's why end null in model.

that's easy verify. inspect modelstate.isvalid property within controller action , notice false. , when inspect model error added model state see this:

the parameter conversion type 'system.decimal' type 'system.nullable`1[[system.double, mscorlib, version=4.0.0.0, culture=neutral, publickeytoken=b77a5c561934e089]]' failed because no type converter can convert between these types.

you may ask, "but why rawvalue property within valueproviderresult of type decimal?". 1 time 1 time again reply lies within asp.net mvc 3 source code (yeah, should have downloaded now). let's take @ jsonvalueproviderfactory.cs file, , more getdeserializedobject method:

public sealed class jsonvalueproviderfactory : valueproviderfactory { ............ private static object getdeserializedobject(controllercontext controllercontext) { if (!controllercontext.httpcontext.request.contenttype.startswith("application/json", stringcomparison.ordinalignorecase)) { // not json request homecoming null; } streamreader reader = new streamreader(controllercontext.httpcontext.request.inputstream); string bodytext = reader.readtoend(); if (string.isnullorempty(bodytext)) { // no json info homecoming null; } javascriptserializer serializer = new javascriptserializer(); object jsondata = serializer.deserializeobject(bodytext); homecoming jsondata; } ............ }

do notice next line:

javascriptserializer serializer = new javascriptserializer(); object jsondata = serializer.deserializeobject(bodytext);

can guess next snippet print on console?

var serializer = new javascriptserializer(); var jsondata = (idictionary<string, object>)serializer .deserializeobject("{\"foo\":2.5}"); console.writeline(jsondata["foo"].gettype());

yep, guessed right, it's decimal.

you may ask, "but why did utilize serializer.deserializeobject method instead of serializer.deserialize in unit test?" it's because asp.net mvc team made design decision implement json request binding using valueproviderfactory, doesn't know type of model.

see how unit test different happens under covers of asp.net mvc 3? should explain why passes, , why controller action doesn't right model value?

json asp.net-mvc-3 model-binding

Comments

Popular posts from this blog

delphi - blogger via idHTTP : error 400 bad request -

c++ - compiler errors when initializing EXPECT_CALL with function which has program_options::variables_map as parameter -

How do I check if an insert was successful with MySQLdb in Python? -