Differentiating null vs. not set properties with HTTP PATCH

March 06, 2020 by Haley Elmendorf

Differentiating null vs. not set properties with HTTP PATCH by Advicent

About the author

Haley Elmendorf

Software developer

Haley Elmendorf is a software developer at Advicent, the financial planning technology provider of choice for nearly 100,000 financial professionals.

Using the HTTP PATCH method is a great option for updating resources with only the fields that need updating, allowing for greater flexibility in your REST API.

In our development of the Identity Management API, the API that powers the NaviPlan business administration tool, we also found that using the HTTP PATCH method poses an interesting problem with nullable fields. How do you know if the user wanted the field to be null or if they simply did not provide it because it did not need updating? The issue came up while patching a user, which is represented by the following model:


{
  "data": {
    "id": 0,
    "userName": "string",
    "email": "string",
    "organizationId": 0,
    "lockoutEnd": "2020-02-13T22:43:09.081Z",
    "accessFailedCount": 0,
    "profile": {
      "effectiveDate": "2020-02-13T22:43:09.081Z",
      "terminationDate": "2020-02-13T22:43:09.081Z",
      "isActive": true,
      "userTemplateId": 0,
      "firstName": "string",
      "lastName": "string",
      "personality": "string",
      "disposition": "string",
      "locale": "string",
      "defaultTeamId": 0,
      "assetAllocationModelId": 0
    }
  }
} 

The UserTemplateId is a nullable int, meaning the user can either have a template or not. They could have a template and be updated to no longer have a template, or they could have a template and we could update another part of the user’s information without wanting to change the template id. By default, in .NET Core, that distinction is not preserved when the JSON is deserialized. So when sending the following model to patch, the JSON model was read with the UserTemplateId field as null, erroneously causing the business logic to set it to null.

Wait a minute, I didn’t set that to null!


{
  "data": {
    "profile": {
      "firstName": "Luna",
      "lastName": "Lovegood"
    }
  }
}

We needed a way to make the API aware of the difference between setting a nullable field to null versus not setting it at all and to customize the way the affected models are deserialized, thereby preserving that information so that the business logic could act on it.

The first option we tried was to change the nullable int types to Optional<int?>. Optional nullable ints have a property HasValue that is a nullable Boolean. HasValue would be set to true if the field was set with a value, false if the field was set with null, and null if the field was not provided in the request. After some contemplation, we decided this solution was not ideal because it changes the request and response format, something we were not willing to do, given it would cause a breaking change that would impact the front end – the part of the project that was already slim on resources and time.

The next option ended up being our solution – a new interface we call IHasPropertyStatus that models can implement, along with a custom JSON converter to deserialize models that implement the interface. The interface has a property “PropertyStatus” that is a dictionary of a string (the property name on the model) and enum that allows for “NotSet”, “SetToNull”, and “SetToValue” values, and the converter handles the logic for how to set each of those. The logic to set those status values goes like this:

 

With the custom converter and interface in place, all you need to do is have the model classes implement the interface. Having each model implement this interface means simply adding the dictionary property to each model and put a JsonIgnore attribute on it – a much simpler and less impactful fix than changing the actual data type of the properties in question. You would then access the property status like this, although a simpler accessor method could be written:

 

The result? Upon PATCH execution, the dictionary is populated with key-value pairs of each property and its status (NotSet, SetToNull , SetToValue) that can be accessed in the business logic to make more informed decisions, thus avoiding setting a field to null that should be left alone!

For more information about how NaviPlan APIs can help your organization tell its story with technology, click here >