I'm using Jackson mixins to only serialize out specific fields.
My ObjectMapper is configured like so:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.addMixIn(Person.class, SyncPerson.class);
mapper.addMixIn(TransactionLog.class, TransactionLogExport.class);
Here are the model classes paired with the JSON mixin objects that I'd like to export:
// Model class
public class Person {
private Long id;
private String email;
private String firstName;
private String lastName;
}
// Desired JSON format. Excludes 'id' field
public interface SyncPerson {
#JsonProperty("firstName")
String getFirstName();
#JsonProperty("lastName")
String getLastName();
#JsonProperty("email")
String getEmail();
}
// Model class
public class TransactionLog {
private long id;
private Integer version;
private Person person;
private Date date;
private EntityAction action;
}
// Desired JSON format. Excludes 'id' field, 'version', 'date'
public interface TransactionLogExport {
#JsonProperty("id")
String getId();
#JsonProperty("person")
Person person();
#JsonProperty("action")
EntityAction getAction();
}
Yet, my tests are showing that the person attribute of the TransactionLog isn't coming through.
#Test
public void testWriteValue() throws Exception {
Person person = new Person();
person.setEmail("a#c.com");
person.setFirstName("A");
person.setLastName("C");
TransactionLog log = new TransactionLog();
log.setId(0L);
log.setAction(EntityAction.CREATE);
log.setPerson(person);
log.setStartValue("start");
log.setEndValue("end");
log.setChanges("change");
String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(log);
System.out.println(prettyJson);
// Prints:
// {
// "id" : 0,
// "action" : "CREATE",
}
}
If I try the same test with a regular ObjectMapper mapper = new ObjectMapper(); instead of the mixin, then I see the full object exported, including the Person with email, names, etc. So something must be wrong with how I've configured the mixin... or else I'm misunderstanding something.
So can anyone help indicate what I could do to export out the subtype 'person' in my mixin?
Thanks!
Finally figured out the issue. The test now prints what we want:
{
“id” : 0,
“person” : {
“email” : “a#c.com”,
“firstName” : “A”,
“lastName” : “C”
},
“action” : “CREATE”
}
The mistake was in TransactionLogExport. It needs to say:
#JsonProperty("person")
Person getPerson();
Instead of:
#JsonProperty("person")
Person person();
I.e. the method needs to start with 'get'.
I have the following Entities
public class Manufacturer
{
int id;
String name;
Country country;
List<Model> models;
}
public class Model
{
int id;
String name;
}
And the following DTO
public class ManufacturerLastModelDto
{
Integer id;
String name;
ModelDto model;
}
public class ModelDto
{
int id;
String name;
}
Now I want to map the Manufacturer to the ManufacturerLastModelDto, like that:
modelMapper.map(manufacturer, ManufacturerLastModelDto.class)
So that only the first entry of the List model will be assigned from manufacturer.
My previous solution was that I had a List of ModelDto's even in the DTO and removed all Entries after Index 0. That was OK, because the ModelMapper mapped the child from Model to ModelDTO automatically.
But only Response wasn't so nice:
models: [
{...}
]
because it was sent as an Array.
Do I need a custom ModelMapper here? If so, how to build it? The tutorial is really complex. Do I need a converter or a TypeMap (or both)?
I am not too familiar with ModelMapper but have used it occasionally.
Yes, you would need to create a converter for the property, and you could either use it with the ModelMapper or a TypeMap. Only caveat being that you will need to map the property yourself, for instance
The converter
Converter<List<Model>, ModelDto> modelConverter = new AbstractConverter<List<Model>, ModelDto>() {
#Override
protected ModelDto convert(List<Model> models) {
if (models == null || models.isEmpty()) {
return null;
}
Model model = models.get(0);
ModelDto dto = new ModelDto();
dto.setId(model.getId());
dto.setName(model.getName());
return dto;
}
};
Now using default model mapper
ModelMapper modelMapper = new ModelMapper();
modelMapper.addConverter(modelConverter);
ManufacturerLastModelDto result = modelMapper.map(manufacturer, ManufacturerLastModelDto.class);
Or using the TypeMap
TypeMap<Manufacturer, ManufacturerLastModelDto> typeMap = modelMapper.typeMap(Manufacturer.class, ManufacturerLastModelDto.class)
.addMappings(mapper ->
mapper.using(modelConverter).map(Manufacturer::getModels, ManufacturerLastModelDto::setModel)
);
ManufacturerLastModelDto result = typeMap.map(manufacturer);
I have this POJO :
public class PlayerDto {
private Long id;
private String name;
private String past;
}
And I have this entity :
public class Player {
private Long id;
private String name;
private List<String> past;
}
How can I map the List<String> past into the String past of the DTO wih MapStruct ? For example the List is containing [ Monty , Boto , Flaouri ] and the String of the DTO has to contain "Monty, Boto, Flaouri" in a single String.
This classic way doesn't work with the target and source :
#Mappings({
#Mapping(target = "past", source = "past"),
})
PlayerDto entityToDto(final Player entity);
Thanks
I guess you need to define a default method in your mapper interface to handle data conversion from List<String> to String. Mapstruct will automatically use the default method.
The default method signature for your mapping should be like this :
String map(List<String> past)
Example :
default String map(List<String> past) {
return past.stream().collect(Collectors.joining(","));
}
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 the following entity, as an example:
#Entity
private class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/* let's assume the following attributes may be null */
private String firstName;
private String lastName;
/* getters and setters ... */
}
My Person repository (Spring Data):
#Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
}
The DTO I use:
private class PersonDTO {
private String firstName;
private String lastName;
/* getters and setters ... */
}
My Spring RestController:
#RestController
#RequestMapping("/api/people")
public class PersonController {
#Autowired
private PersonRepository people;
#Transactional
#RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody PersonDTO dto) {
// get the entity by ID
Person p = people.findOne(personId); // we assume it exists
// update ONLY entity attributes that have been defined
if(/* dto.getFirstName is defined */)
p.setFirstName = dto.getFirstName;
if(/* dto.getLastName is defined */)
p.setLastName = dto.getLastName;
return ResponseEntity.ok(p);
}
}
Request with missing property
{"firstName": "John"}
Expected behaviour: update firstName= "John" (leave lastName unchanged).
Request with null property
{"firstName": "John", "lastName": null}
Expected behaviour: update firstName="John" and set lastName=null.
I cannot distinguish between these two cases, sincelastName in the DTO is always set to null by Jackson.
Note:
I know that REST best practices (RFC 6902) recommend using PATCH instead of PUT for partial updates, but in my particular scenario I need to use PUT.
Another option is to use java.util.Optional.
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Optional;
#JsonInclude(JsonInclude.Include.NON_NULL)
private class PersonDTO {
private Optional<String> firstName;
private Optional<String> lastName;
/* getters and setters ... */
}
If firstName is not set, the value is null, and would be ignored by the #JsonInclude annotation. Otherwise, if implicitly set in the request object, firstName would not be null, but firstName.get() would be. I found this browsing the solution #laffuste linked to a little lower down in a different comment (garretwilson's initial comment saying it didn't work turns out to work).
You can also map the DTO to the Entity with Jackson's ObjectMapper, and it will ignore properties that were not passed in the request object:
import com.fasterxml.jackson.databind.ObjectMapper;
class PersonController {
// ...
#Autowired
ObjectMapper objectMapper
#Transactional
#RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody PersonDTO dto
) {
Person p = people.findOne(personId);
objectMapper.updateValue(p, dto);
personRepository.save(p);
// return ...
}
}
Validating a DTO using java.util.Optional is a little different as well. It's documented here, but took me a while to find:
// ...
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
// ...
private class PersonDTO {
private Optional<#NotNull String> firstName;
private Optional<#NotBlank #Pattern(regexp = "...") String> lastName;
/* getters and setters ... */
}
In this case, firstName may not be set at all, but if set, may not be set to null if PersonDTO is validated.
//...
import javax.validation.Valid;
//...
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody #Valid PersonDTO dto
) {
// ...
}
Also might be worth mentioning the use of Optional seems to be highly debated, and as of writing Lombok's maintainer(s) won't support it (see this question for example). This means using lombok.Data/lombok.Setter on a class with Optional fields with constraints doesn't work (it attempts to create setters with the constraints intact), so using #Setter/#Data causes an exception to be thrown as both the setter and the member variable have constraints set. It also seems better form to write the Setter without an Optional parameter, for example:
//...
import lombok.Getter;
//...
#Getter
private class PersonDTO {
private Optional<#NotNull String> firstName;
private Optional<#NotBlank #Pattern(regexp = "...") String> lastName;
public void setFirstName(String firstName) {
this.firstName = Optional.ofNullable(firstName);
}
// etc...
}
There is a better option, that does not involve changing your DTO's or to customize your setters.
It involves letting Jackson merge data with an existing data object, as follows:
MyData existingData = ...
ObjectReader readerForUpdating = objectMapper.readerForUpdating(existingData);
MyData mergedData = readerForUpdating.readValue(newData);
Any fields not present in newData will not overwrite data in existingData, but if a field is present it will be overwritten, even if it contains null.
Demo code:
ObjectMapper objectMapper = new ObjectMapper();
MyDTO dto = new MyDTO();
dto.setText("text");
dto.setAddress("address");
dto.setCity("city");
String json = "{\"text\": \"patched text\", \"city\": null}";
ObjectReader readerForUpdating = objectMapper.readerForUpdating(dto);
MyDTO merged = readerForUpdating.readValue(json);
Results in {"text": "patched text", "address": "address", "city": null}
Note that text and city were patched (city is now null) and that address was left alone.
In a Spring Rest Controller you will need to get the original JSON data instead of having Spring deserialize it in order to do this. So change your endpoint like this:
#Autowired ObjectMapper objectMapper;
#RequestMapping(path = "/{personId}", method = RequestMethod.PATCH)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody JsonNode jsonNode) {
RequestDTO existingData = getExistingDataFromSomewhere();
ObjectReader readerForUpdating = objectMapper.readerForUpdating(existingData);
RequestDTO mergedData = readerForUpdating.readValue(jsonNode);
...
}
Use boolean flags as jackson's author recommends.
class PersonDTO {
private String firstName;
private boolean isFirstNameDirty;
public void setFirstName(String firstName){
this.firstName = firstName;
this.isFirstNameDirty = true;
}
public String getFirstName() {
return firstName;
}
public boolean hasFirstName() {
return isFirstNameDirty;
}
}
Actually,if ignore the validation,you can solve your problem like this.
public class BusDto {
private Map<String, Object> changedAttrs = new HashMap<>();
/* getter and setter */
}
First, write a super class for your dto,like BusDto.
Second, change your dto to extend the super class, and change the
dto's set method,to put the attribute name and value to the
changedAttrs(beacause the spring would invoke the set when the
attribute has value no matter null or not null).
Third,traversal the map.
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<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);
}
}
}
}
Maybe too late for an answer, but you could:
By default, don't unset 'null' values. Provide an explicit list via query params what fields you want to unset. In such a way you can still send JSON that corresponds to your entity and have flexibility to unset fields when you need.
Depending on your use case, some endpoints may explicitly treat all null values as unset operations. A little bit dangerous for patching, but in some circumstances might be an option.
Another solution would be to imperatively deserialize the request body. By doing it, you will be able to collect user provided fields and selectively validate them.
So your DTO might look like this:
public class CatDto {
#NotBlank
private String name;
#Min(0)
#Max(100)
private int laziness;
#Max(3)
private int purringVolume;
}
And your controller can be something like this:
#RestController
#RequestMapping("/api/cats")
#io.swagger.v3.oas.annotations.parameters.RequestBody(
content = #Content(schema = #Schema(implementation = CatDto.class)))
// ^^ this passes your CatDto model to swagger (you must use springdoc to get it to work!)
public class CatController {
#Autowired
SmartValidator validator; // we'll use this to validate our request
#PatchMapping(path = "/{id}", consumes = "application/json")
public ResponseEntity<String> updateCat(
#PathVariable String id,
#RequestBody Map<String, Object> body
// ^^ no Valid annotation, no declarative DTO binding here!
) throws MethodArgumentNotValidException {
CatDto catDto = new CatDto();
WebDataBinder binder = new WebDataBinder(catDto);
BindingResult bindingResult = binder.getBindingResult();
List<String> patchFields = new ArrayList<>();
binder.bind(new MutablePropertyValues(body));
// ^^ imperatively bind to DTO
body.forEach((k, v) -> {
patchFields.add(k);
// ^^ collect user provided fields if you need
validator.validateValue(CatDto.class, k, v, bindingResult);
// ^^ imperatively validate user input
});
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
// ^^ this can be handled by your regular exception handler
}
// Here you can do normal stuff with your catDto.
// Map it to cat model, send to cat service, whatever.
return ResponseEntity.ok("cat updated");
}
}
No need for Optional's, no extra dependencies, your normal validation just works, your swagger looks good. The only problem is, you don't get proper merge patch on nested objects, but in many use cases that's not even required.
Probably to late but following code works for me to distinguish between null and not provided values
if(dto.getIban() == null){
log.info("Iban value is not provided");
}else if(dto.getIban().orElse(null) == null){
log.info("Iban is provided and has null value");
}else{
log.info("Iban value is : " + dto.getIban().get());
}
I have these two classes:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",scope = Rol.class)
public class Rol extends MyEntity implements Serializable {
private Integer id;
private String rolName;
public Rol(Integer id, String rolName) {
this.id = id;
this.rolName = rolName;
}
...
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",scope = User.class)
public class User extends MyEntity implements Serializable {
private Integer id;
private String name;
private List<Rol> rolList;
public User(Integer id, String name, List<Rol> rolList) {
this.id = id;
this.name = name;
this.rolList = rolList;
}
...
}
and I try to serialize and deserialize the user object as following
Rol rol1 = new Rol(1, "MyRol");
Rol rol2 = new Rol(1, "MyRol");
List<Rol> rolList = new ArrayList();
rolList.add(rol1);
rolList.add(rol2);
user = new User(1, "MyUser", rolList);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(user);
User userJson = mappe.readValue(jsonString, User.class);
and the JsonMappingException: Already had POJO for id is produced. Why?
When I review the json result of the serialization I see that the result is
{"id": 1,"name": "MyName","rolList": [{"id": 1,"rolName": "MyRol"},{"id": 1,"rolName": "MyRol"}]}
when the result should be
{"id": 1,"name": "MyName","rolList": [{"id": 1,"rolName": "MyRol"},1]}
because rol1 and rol2 are different instances of the same POJO identifier with id 1.
How can I avoid the JsonMappingException? In my project I have some different instances of the same POJO. I can guarantee that if the id's are equal -> objects are equal.
Excuse me for my bad English.
For anyone returning to this question, it looks like there's option to do this with a custom ObjectIdResolver in Jackson. You can specify this on the #JsonIdentityInfo annotation, e.g. :
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name",
resolver = CustomObjectIdResolver.class)
Then perhaps wrap the normal SimpleObjectIdResolver class to get going and customise bindItem().
In my case I wanted to avoid overlapping objectIds, so cleared down the references when I started a new Something:
public class CustomObjectIdResolver implements ObjectIdResolver {
private ObjectIdResolver objectIdResolver;
public CustomObjectIdResolver() {
clearReferences();
}
#Override
public void bindItem(IdKey id, Object pojo) {
// Time to drop the references?
if (pojo instanceof Something)
clearReferences();
objectIdResolver.bindItem(id, pojo);
}
#Override
public Object resolveId(IdKey id) {
return objectIdResolver.resolveId(id);
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == getClass();
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new CustomObjectIdResolver();
}
private void clearReferences() {
objectIdResolver = new SimpleObjectIdResolver();
}
}
Jackson expects in this case different id for different class instances. There has been a previous discussion at github here. Overriding hashCode and equals will not help. Object references must match for equal id.
Options
Reuse Rol instances instead of making new ones with equal fields. As a bonus you will also save memory.
Modify the application logic so that it doesn't depend on #JsonIdentityInfo