I\'m trying to distinguish between null values and not provided values when partially updating an entity with PUT request method in Spring Rest Controller.
Consider
I have tried to solve the same problem. I found it quite easy to use JsonNode as the DTOs. This way you only get what is submitted.
You will need to write a MergeService yourself that does the actual work, similar to the BeanWrapper. I haven't found an existing framework that can do exactly what is needed. (If you use only Json requests you might be able to use Jacksons readForUpdate method.)
We actually use another node type as we need the same functionality from "standard form submits" and other service calls. Additionally the modifications should be applied within a transaction inside something called EntityService.
This MergeService will unfortunately become quite complex, as you will need to handle properties, lists, sets and maps yourself :)
The most problematic piece for me was to distinguish between changes within an element of a list/set and modifications or replacements of lists/sets.
And also validation will not be easy as you need to validate some properties against another model (the JPA entities in my case)
EDIT - Some mapping code (pseudo-code):
class SomeController {
@RequestMapping(value = { "/{id}" }, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public void save(
@PathVariable("id") final Integer id,
@RequestBody final JsonNode modifications) {
modifierService.applyModifications(someEntityLoadedById, modifications);
}
}
class ModifierService {
public void applyModifications(Object updateObj, JsonNode node)
throws Exception {
BeanWrapperImpl bw = new BeanWrapperImpl(updateObj);
Iterator fieldNames = node.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
Object valueToBeUpdated = node.get(fieldName);
Class> propertyType = bw.getPropertyType(fieldName);
if (propertyType == null) {
if (!ignoreUnkown) {
throw new IllegalArgumentException("Unkown field " + fieldName + " on type " + bw.getWrappedClass());
}
} else if (Map.class.isAssignableFrom(propertyType)) {
handleMap(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects);
} else if (Collection.class.isAssignableFrom(propertyType)) {
handleCollection(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects);
} else {
handleObject(bw, fieldName, valueToBeUpdated, propertyType, createdObjects);
}
}
}
}