I tried to solve the same problem. It was pretty easy for JsonNode to use JsonNode as a DTO. This way you only get what is sent.
You will need to write the MergeService yourself, which does the actual work, similar to BeanWrapper. I did not find an existing structure that can do exactly what is needed. (If you use only Json requests, you can use the Jacksons readForUpdate method.)
In fact, we use a different type of node, because we need the same functionality from the "standard forms" and other service calls. In addition, modifications must be applied within a transaction within what is called an EntityService .
This MergeService , unfortunately, will become quite complicated, since you will have to process the properties, lists, sets and maps yourself :)
The most problematic for me was the difference between changes to a list / set item and modifications or replacements of lists / sets.
And also the check will not be simple, since you need to check some properties against another model (JPA entities in my case)
EDIT - code code (pseudocode):
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<String> 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); } } } }