is it normal that the MVC deserializer is converting json blank strings to nulls?

1,646 views
Skip to first unread message

gaffe

unread,
Apr 19, 2012, 7:05:55 PM4/19/12
to knock...@googlegroups.com
When I make an ajax post and send a json object to the server that contains a blank string, when MVC3 deserialized the object this property contains null instead of the blank string. Is this normal behavior or does this mean there is a bug in my code?

I am making this ajax call:

   $.ajax({
        url
: "/test/whatever",
        type
: 'post',
        contentType
: 'application/json'
        data
: {"fieldone":'test',"fieldtwo":'',"fieldthree":null,"fieldfour":'test'}
   
});

in the controller I have:

    [HttpPost]
   
public string Whatever(MyModel object)
   
{
       
//fieldone, fieldtwo and fieldthree are all type string  
       
//at this point:
       
// object.fieldone contains the string 'test'
       
// object.fieldtwo is NULL, but WHY?! and how can I have it correctly deserialize to blank string
       
// object.fieldthree is correctly null, which is fine
       
// I have no fieldfour in my object, so it is ignored, that makes sense.
       
// object.newfield is correctly null, because I didn't pass a value for it in my JS
   
}

So, does anyone know why blank strings are turning into nulls in this case? I found this post which talks about a bug in javascriptserializer for nullable properties:

http://blog.js-development.com/2012/02/aspnet-mvc3-doesnt-deserialize-nullable.html

But my case is even simpler than that, I just want to deseralize and object that contains descriptive fields, some of which might be blank strings.

I need to tell the difference between blank strings and nulls so that I can throw an exception if I get any nulls (my client js code isn't in sync with the c# object ususally when this happens and I want to know about it).

gaffe

unread,
Apr 26, 2012, 11:04:39 PM4/26/12
to knock...@googlegroups.com
In case anyone has this problem, turns out that the built in deserializer isn't so great. Here is how I solved it:

JSON.NET works better, just reference it and use this:

public class MyCustomModelBinder : IModelBinder
{
    private IModelBinder fallbackModelBinder;
    public MyCustomModelBinder(IModelBinder fallbackModelBinder)
    {
        this.fallbackModelBinder = fallbackModelBinder;
    }
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        //if it's a GET or content-type is not json, then use the built in one. If ModelType is System.String this the json object should deserializes into separate strings rather than a class, the built in one does this for you
        if (controllerContext.HttpContext.Request.HttpMethod.Equals("GET", StringComparison.InvariantCultureIgnoreCase) || !controllerContext.HttpContext.Request.ContentType.ToString().Contains("json") || bindingContext.ModelType.ToString()=="System.String")
        {
            return this.fallbackModelBinder.BindModel(controllerContext, bindingContext);
        }
        else
        {
            //ONLY do this for POST,PUT,DELETE requests that transmit application/json
            string bodyText;
            using (var stream = controllerContext.HttpContext.Request.InputStream)
            {
                stream.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(stream))
                    bodyText = reader.ReadToEnd();
            }
 
            if (string.IsNullOrEmpty(bodyText))
                return (null);
          //  JsonSerializerSettings settings = new JsonSerializerSettings();
          //  settings.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore;
            
            return JsonConvert.DeserializeObject(bodyText, bindingContext.ModelType);
        }
    }
}


This still doesn't serialize 2D arrays or a number of other things that would be very convenient, but it's much less buggy than the default deserializer. This custom model binder handles backwards compatibility much better than the one in the link above.

pja...@nevo.com

unread,
Jul 24, 2012, 11:17:41 AM7/24/12
to knock...@googlegroups.com
I just ran into this behavior as well.  It sounds like you have worked through it, but I figured I would shed some additional light for those that might stumble upon this thread.

I'm pretty sure the behavior is a result of the MVC DefaultModelBinder that uses its ModelMetadata.ConvertEmptyStringToNull property (defaulted to TRUE) for binding to complex objects, and the result of Json deserialization gets passed through that.  So the built in JavaScriptSerializer is actually behaving the same way as JSON.NET, but your revised approach has MyCustomModelBinder avoiding the additional logic in the DefaultModelBinder.GetPropertyValue, and hence doesn't translate from "" to NULL.

The following basic MVC example demonstrates the same behavior that binding to a complex object results in empty strings submitted to the server getting assigned as NULL in the resulting model object ("anObject" in the example below).  I guess its still an open question of whether the Json deserialization in the MVC pipeline should honor the ModelMetadata.ConvertEmptyStringToNull, but I would guess that Microsoft wanted the default JSON channel to behave the same way as if the data was being form posted...

        [HttpPost]
        public ActionResult Index(AnObject anObject, string aSimpleBinding, string aSimpleEmptyBinding)
        {
            return View();
        }

        public class AnObject
        {
            public string AProperty { get; set; }
            public string ABlankProperty { get; set; }
        }


// in the view
@using (Html.BeginForm()) {
    <div>
    Demonstrate a simple binding working: @Html.TextBox("aSimpleBinding", "aValue")<br />
    A blank simple binding results in "" on the server: @Html.TextBox("aSimpleEmptyBinding", "")<br />
    Demonstrate a property binding to a complex object: @Html.TextBox("anObject.AProperty", "anotherValue")<br />
    ** A blank value on a complex object gets bound to NULL **: @Html.TextBox("anObject.ABlankProperty","")<br />
    <input type="submit" value="Click me" />
    </div>
}

Pete

qs

unread,
May 16, 2013, 4:50:26 PM5/16/13
to knock...@googlegroups.com
Thanks!  This helped a lot.  Stupid default deserializer..

Reply all
Reply to author
Forward
0 new messages