Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON serializer creates an empty object on failed deserialization instead of null

In WebAPI, I've noticed an inconsistency that is messing with our validation practices. If you send a bad body/payload with a POST in xml, deserialization fails and you get an null pointer. If you send a bad body/payload in JSON, you get an empty object instead. It is misleading and I don't like it. Is there a way to force a null pointer with a failed json deserialization??

UPDATE: I'm not having a deserialization problem. I'm having a behavior problem that seems to be a difference between the DataContractSerializer and the Json.net serializer. When xml fails to deserialize, the payload is null. However, when Json fails to deserialize, it seems that it is instantiating a default instance of the expected payload.

Example of a bad xml payload: enter image description here

Example of the same call using a bad json payload (payload is not null. instead it is a default instance of the payload class)

enter image description here

like image 364
Sinaesthetic Avatar asked Apr 23 '13 20:04

Sinaesthetic


2 Answers

By default Web.API used the MissingMemberHandling.Ignore setting in the JsonMediaTypeFormatter.

You need to set it to MissingMemberHandling.Error with:

GlobalConfiguration.Configuration
   .Formatters.JsonFormatter
   .SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;

and you should get null when sending JSON like:

{
   "somenotexistingprop": ""
}

However if you send a completely empty JSON object: {} then you will still get an object with empty properties and not null. Because JsonConvert.DeserializeObject returns a empty object if it deserializes an empty JSON (see this unit test of github).

like image 132
nemesv Avatar answered Sep 28 '22 02:09

nemesv


After one of my team members helped me understand why this is the case, following are some notes about this scenario:

  1. We by default do not have "MissingMemberHandling.Error" on Json formatter because this can help in scenarios where you a newer version of client sending data with extra members to an older version of service. The older version of service should still be able to accept this request and ignore the extra properties. This behavior is consistent with how Xml formatter also behaves.

  2. @Sinaesthetic: if you are setting "MissingMemberHandling.Error" and do not want to rely on "ModelState.IsValid" check only, then you could alway check for ModelState's Keys property to figure out if your request is indeed invalid because of a missing member or something else.

Example below:

public class Customer
{
    public int Id { get; set; }

    [MaxLength(5)]
    public string Name { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string Line1 { get;set;}
}

//action
public void Post([FromBody]Customer customer)
    {
        if (!ModelState.IsValid)
        {
            ModelStateDictionary msd = new ModelStateDictionary();

            foreach (string key in ModelState.Keys)
            {
                if (ModelState[key].Errors.Count > 0)
                {
                    foreach (ModelError error in ModelState[key].Errors)
                    {
                        Exception ex = error.Exception;

                        if (ex != null 
                            && typeof(JsonSerializationException).IsAssignableFrom(ex.GetType()) 
                            && ex.Message.StartsWith("Could not find member"))
                        {
                            msd.AddModelError(key, ex);
                        }
                    }
                }
            }

            if (msd.Count > 0)
            {
                throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, msd));
            }
        }

        //process the supplied customer
    }
like image 33
Kiran Avatar answered Sep 28 '22 01:09

Kiran