Fabrizio Stellato
Fabrizio Stellato

Reputation: 1901

Jax rs - PATCH method behaviour with null values

I have a REST web app java EE8 application.

Let's say I have this:

class PersonRequest {
     String email;
     String name;
     String surname;
     String mobile;

     .. getter and setter
}

I want to create a resourche method that updates ONLY requested properties, therefore I use PATCH method.

A JSON request example:

PATCH
{
    "mobile": "+3494441122" // update only mobile
}

Will modify only mobile field and leave other fields untouched.

THE ISSUE

How to deal with null values with PATCH method ?

Should I consider to turn blank the field or just ignore it ?

I'm worried about the the first case as I don't know if is there any way to recognize the difference, because either if I don't pass the field or i specify the null value, the Person's property field will be a null value.

Any hints?

Upvotes: 3

Views: 3501

Answers (2)

Nikos Paraskevopoulos
Nikos Paraskevopoulos

Reputation: 40318

I'll go ahead and post a solution based on Java 8's Optional. First of all the problem it is supposed to solve is mapping JSON undefined values to Java for the HTTP PATCH case: null in Java is inadequate as it might correspond to a null or undefined JSON value. And in the PATCH case we want to treat the 2 JSON values in a different way: null actually sets to Java null, undefined leaves the value as is.

I tested it on WildFly 15, so I guess it will probably work on WildFly servers and anything else using RestEasy (e.g. Quarkus) - but I have not tested! I skimmed through the JAX-RS 2.1 specs and did not find any explicit mentioning of Optional and how it should be handled, so beware this might be a RestEasy-only solution! Even worse, it might be related to the exact tool for handling the JSON, so could work e.g. with Jackson but not with JSON-B.

Also there is a question about correct style and using Optional as intended; see the link in the comments of the question. Although I must admit this is NOT how I would normally construct a generic Java Bean, I believe this solution is good enough for this special case. Another solution I have tried in the past was keeping a boolean variable alongside each normal variable (e.g. nameIsSet). I think this and other solutions are in the end more cumbersome than the one outlined here.

The DTO:

class PersonPatchRequest {
     Optional<String> email;
     Optional<String> name;
     Optional<String> surname;
     Optional<String> mobile;

     .. getter and setter for the Optional, e.g.:


    public Optional<String> getName() {
        return name;
    }

    public void setName(Optional<String> name) {
        this.name = name;
    }
}

Then sending the following object:

{
    "name": "Bob",
    "surname": null
}

E.g. as:

curl -X PATCH http://...  -H "Accepts: application/json" -H "Content-Type: application/json"    \
     -d "{\"name\":\"Bob\",\"surname\":null}"

Will result in the following data in Java:

PersonPatchRequest {
    email: null,
    name: Optional["Bob"]
    surname: Optional.empty
    mobile: null
}

Optional.empty values are the real nulls the client sent, null values are the undefined.

Upvotes: 1

Ayyub Kolsawala
Ayyub Kolsawala

Reputation: 849

Not the best solution but it should get you going.

import java.util.Iterator;

import org.json.JSONObject;

public class Test{
    public static void main(String[] args) {

        /** We will pass the Body of the request through a series of conditions listed below.
        1. Check if the key exists,
        2. Check if the value is not null,
        3. Check if the value is not "null"
        **/

        JSONObject requestBody = new JSONObject("{\"mobile\":\"+3494441122\",\"email\":\"email\",\"name\":\"null\",\"surname\":null}");

        JSONObject updateObject = new JSONObject();

        Iterator<String> ittr = requestBody.keys();
        while(ittr.hasNext()) {

            String key = ittr.next();
            if(requestBody.has(key)) {
                if(!requestBody.isNull(key)) {
                    String value = requestBody.get(key).toString();
                    if(!value.equalsIgnoreCase("null")) {
                        //If these cases are matched, only then allow the value to be updated.
                        updateObject.put(key, value);
                    }
                }

            }

        }
        System.out.println(updateObject);

    }
}

OUTPUT

{"mobile":"+3494441122","email":"email"}

You can also use GSON libary for doing so which is a better approach but it will accept "null" values unlike the previous approach.

Main Class

import org.json.JSONObject;

public class Test{
    public static void main(String[] args) {

        JSONObject requestBody = new JSONObject("{\"mobile\":\"+3494441122\",\"email\":\"email\",\"name\":\"null\",\"surname\":null}");

        PersonRequest personRequest = new PersonRequest().fromJson(requestBody.toString() );
        System.out.println(personRequest.toJson());

    }
}

POJO Class

import org.json.JSONObject;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class PersonRequest {
    String email;
    String name;
    String surname;
    String mobile;
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSurname() {
        return surname;
    }
    public void setSurname(String surname) {
        this.surname = surname;
    }
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public JSONObject toJson() {
        return new JSONObject(new Gson().toJson(this));
    }

    public  PersonRequest fromJson(String json) {
        Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .create(); 

        return gson.fromJson(json, this.getClass());
    }
}

Output :

{"name":"null","mobile":"+3494441122","email":"email"}

The difference is just that now name has a null String.

Upvotes: 1

Related Questions