REST API for updating informations with empty or null values

11,792

Solution 1

You could deserialize your JSON to a Map. This way, if a property has not been sent, the property is not present in the Map. If its null, its inside the map will a null value.

  ObjectMapper mapper = new ObjectMapper();
  TypeReference<HashMap<String, Object>> typeReference = new TypeReference<>() {};
  HashMap<String, Object> jsonMap = mapper.readValue(json, typeReference);
  jsonMap.entrySet().stream().map(Map.Entry::getKey).forEach(System.out::println);

Not a very convenient solution, but it might work for you.

Solution 2

A common technique is to track changes on the entity POJO.

  1. Load Dog with color = black, size = null and age = null
  2. Set size to null (the setter will mark this field as changed)
  3. Run update SQL

The POJO will have an internal state knowning that size was changed, and thus include that field in the UPDATE. age, on the other hand, was never set, and is thus left unchanged. jOOQ works like that, I'm sure there's others.

Solution 3

Suppose we extend the database by five more fields. The user of the API makes a GET, gets the 15 fields, but can only read the 10 fields he knows on his page (because he hasn't updated his side yet). Then he changes some of the 10 fields and sends them back via PUT. We would then update only the 10 fields on our site and the 5 new fields would be emptied from the database.

So let's start with an example - what would happen on the web, where clients are interacting with your API via HTML rendered in browsers. The client would GET a form, and that form would have input controls for each of the fields. Client updates the fields in the form, submits it, and you apply those changes to your database.

When you want to extend the API to include more fields, you add those fields to the form. The client doesn't know about those fields. So what happens?

One way to manage this is that you make sure that you include in the form the correct default values for the new fields; then, if the client ignores the new fields, the correct value will be returned when the form is submitted.

More generally, the representations we exchange in our HTTP payloads are messages; if we want to support old clients, then we need the discipline of evolving the message schema in a backwards compatible way, and our clients have to be written with the understanding that the message schema may be extended with additional fields.

The person using the API does not know that there is a new field in JSON for the salary.

The same idea holds here - the new representation includes a field "salary" that the client doesn't know about, so it is the responsibility of the client to forward that data back to you unchanged, rather than just dropping it on the floor assuming it is unimportant.

There's a bunch of prior art on this from 15-20 years ago, because people writing messages in XML were facing exactly the same sort of problems. They have left some of their knowledge behind. The easiest way to find it is to search for some key phases; for instance must ignore or must forward.

See:

Events in an event store have the same kinds of problems. Greg Young's book Versioning in an Event Sourced System covers a lot of the same ground (representations of events are also messages).

Share:
11,792

Related videos on Youtube

Hauke
Author by

Hauke

Updated on September 16, 2022

Comments

  • Hauke
    Hauke over 1 year

    I have a general question about how best to build an API that can modify records in a database.

    Suppose we have a table with 10 columns and we can query these 10 columns using REST (GET). The JSON response will contain all 10 fields. This is easy and works without problems.

    The next step is that someone wants to create a new record via POST. In this case the person sends only 8 of the 10 fields in the JSON Request. We would then only fill the 8 fields in the database (the rest would be NULL). This also works without problems.

    But what happens if someone wants to update a record? We see here different possibilities with advantages and disadvantages.

    1. Only what should be updated is sent. Problem: How can you explicitly empty / delete a field? If a "NULL" is passed in the JSON, we get NULL in the object, but any other field that is not passed is NULL as well. Therefore we cannot distinguish which field can be deleted and which field cannot be touched.

    2. The complete object is sent. Problem: Here the object could be fetched via a GET before, changed accordingly and returned via PUT. Now we get all information back and could write the information directly back into the database. Because empty fields were either already empty before or were cleared by the user.

    What happens if the objects are extended by an update of the API. Suppose we extend the database by five more fields. The user of the API makes a GET, gets the 15 fields, but can only read the 10 fields he knows on his page (because he hasn't updated his side yet). Then he changes some of the 10 fields and sends them back via PUT. We would then update only the 10 fields on our site and the 5 new fields would be emptied from the database.

    Or do you have to create a separate endpoint for each field? We have also thought about creating a map with key / value, what exactly should be changed.

    About the technique: We use the Wildfly 15 with Resteasy and Jackson.

    For example:

    Database at the beginning

    +----+----------+---------------+-----+--------+-------+
    | ID | Name     | Country       | Age | Weight | Phone |
    +----+----------+---------------+-----+--------+-------+
    | 1  | Person 1 | Germany       | 22  | 60     | 12345 |
    | 2  | Person 2 | United States | 32  | 78     | 56789 |
    | 3  | Person 3 | Canada        | 52  | 102    | 99999 |
    +----+----------+---------------+-----+--------+-------+
    

    GET .../person/2

    {
       "id" : 2,
       "name" : "Person 2",
       "country" : "United States",
       "age" : 22,
       "weight" :62,
       "phone": "56789"
    }
    

    Now I want to update his weight and remove the phone number

    PUT .../person/2

    {
       "id" : 2,
       "name" : "Person 2",
       "country" : "United States",
       "age" : 22,
       "weight" :78
    }
    

    or

    {
       "id" : 2,
       "name" : "Person 2",
       "country" : "United States",
       "age" : 22,
       "weight" :78,
       "phone" : null
    }
    

    Now the database should look like this:

    +----+----------+---------------+-----+--------+-------+
    | ID | Name     | Country       | Age | Weight | Phone |
    +----+----------+---------------+-----+--------+-------+
    | 1  | Person 1 | Germany       | 22  | 60     | 12345 |
    | 2  | Person 2 | United States | 32  | 78     | NULL  |
    | 3  | Person 3 | Canada        | 52  | 102    | 99999 |
    +----+----------+---------------+-----+--------+-------+
    

    The problem is

    We extend the table like this (salery)

    +----+----------+---------------+-----+--------+--------+-------+
    | ID | Name     | Country       | Age | Weight | Salery | Phone |
    +----+----------+---------------+-----+--------+--------+-------+
    | 1  | Person 1 | Germany       | 22  | 60     | 1929   | 12345 |
    | 2  | Person 2 | United States | 32  | 78     | 2831   | NULL  |
    | 3  | Person 3 | Canada        | 52  | 102    | 3921   | 99999 |
    +----+----------+---------------+-----+--------+--------+-------+
    

    The person using the API does not know that there is a new field in JSON for the salary. And this person now wants to change the phone number of someone again, but does not send the salary. This would also empty the salary:

    {
       "id" : 3,
       "name" : "Person 3",
       "country" : "Cananda",
       "age" : 52,
       "weight" :102,
       "phone" : null
    }
    
    
    +----+----------+---------------+-----+--------+--------+-------+
    | ID | Name     | Country       | Age | Weight | Salery | Phone |
    +----+----------+---------------+-----+--------+--------+-------+
    | 1  | Person 1 | Germany       | 22  | 60     | 1929   | 12345 |
    | 2  | Person 2 | United States | 32  | 78     | 2831   | NULL  |
    | 3  | Person 3 | Canada        | 52  | 102    | NULL   | NULL  |
    +----+----------+---------------+-----+--------+--------+-------+
    

    And salary should not be null, because it was not set inside the JSON request