Search code examples
javaspringpostjacksonpojo

How to create a postmapping method in spring so that clients need only enter a partial object instead of full object?


Here is my pojo

// Removed getters, setters, constructors for brevity, Also don't worry about Movie pojo
@Document
public class WatchList {
    @Id
    private String _id;
    private List<Movie> currentlyWatching;
    private List<Movie> completed;
}

Now in my rest controller I have a postmapping method like this

// Here id is the id of watchList I want to update
@PostMapping("/{id}/update")
public void updateItem(@PathVariable String id, @RequestBody WatchList watchList){
    WatchList old = watchListRepo.findById(id).get();  
    // Now update old with contents from watchList. However only change those fields that are sent from @RequestBody. How to do this?
}

So from postman, I am sending a post request to this endpoint(with the id to change of course) with body like this

{
    "currentlyWatching" : [...some new stuff]
}

However, my old watchList pojo already has stuff like this:

{
    "currentlyWatching" : [...some old stuff],
    "completed" : [...some old stuff],
}

Now once this method gets done, I want my pojo to turn into this

{
    "currentlyWatching" : [...some new stuff], // so this gets updated from post request
    "completed" : [...some old stuff], // this stays the same since post request body did not contain this field called completed
}

So, I essentially only want to update currentlyWatching field when client sends a post request with body containing currentlyWatching. Now I can of course check for fields and map it manually, but my WatchList is not as simple as shown here. It has more than 10 fields(not just currentlyWatching and completed). I want some dynamic easy way to do this. Also manual way of doing this would probably not be best design practice anyway.

Extra Info: I am using spring data mongo so that @Document annotation or repository class(extends MongoRepository) are from mongo jars. Also if it matters at all, the Movie pojo is another pojo that has nested properties. However I want a way to do this dynamically, so Movie pojo could be anything. Hence the solution that we figure out here should work for any Movie pojo Schema. I have also heard about something called BeanAwareUtils, is that something that could be used here?


Solution

  • Here is one approach that I found after searching online. You can use BeanUtils. Here is maven dependency for it(make sure to check the updated version when you are using it).

    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.9.3</version>
    </dependency>
    

    Now you need to create another class(className can be anything) as follows:

    public class NullAwareBeanArrayUtilsBean extends BeanUtilsBean {
    
        @Override
        public void copyProperty(Object dest, String name, Object value) throws IllegalAccessException, InvocationTargetException {
            if (value == null || (value instanceof List<?> && ((List<?>) value).size() <= 0))
                return;
            super.copyProperty(dest, name, value);
        }
    
    }
    

    And when you want to use it anywhere else in your code, use it like this:

    BeanUtilsBean notNull = new NullAwareBeanArrayUtilsBean();
    notNull.copyProperties(dest, original);
    

    Explanation

    BeanUtils is a library which has one method to copy properties from one pojo to another pojo. It has a copyProperties method which internally calls copyProperty method. Here we have created a subclass called NullAwareBeanArrayUtilsBean which extends BeanUtilsBean and we have given a custom functionality to one of its methods named copyProperty.

    Remember that this method is called internally from copyProperties method. You can try console logging name in this copyProperty method to see that name refers to each field from your pojo.

    What are we doing in the custom overriden copyProperty method?

    Consider this line

    BeanUtilsBean notNull = new NullAwareBeanArrayUtilsBean();
    notNull.copyProperties(dest, original);
    

    Here we are saying that we want to copy stuff from original pojo to dest pojo.

    For each field in our pojo, copyProperties internally calls our custom overriden method named copyProperty.

    Consider copyProperty method's first line:

    if (value == null || (value instanceof List<?> && ((List<?>) value).size() <= 0)) return;
    

    Here we are checking if value is null or if value is a List and is that list empty, then we are skipping the copying operation. Therefore with this approach, if user sends a partial object from postman(for instance), we will copy only those fields in which the user has put some stuff all the while not changing other fields(which already have old content) in our destination pojo.