In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}
Related
Gotta question regarding mapStruct. I have case where I extend class from base entity and not sure how to map it. Here is my case.
BaseEntity:
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
private Long id;
}
BaseDto:
public class BaseDto {
private Long id;
}
UserEntity:
public class User extends BaseEntity {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
UserDto:
public class UserDto extends BaseDto {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
And mapper is like this:
#Mapper(uses = {BaseMapper.class})
public interface UserMapper {
User userDtoToUser(UserDto userDto);
UserDto userToUserDto(User user);
}
BaseMapper:
#Mapper
public interface BaseMapper {
BaseEntity dtoToEntity(BaseDto baseDto);
BaseDto entityToDto(BaseEntity baseEntity);
}
Problem is that I don't get ID property mapped.
Thank you for your time.
EDIT:
There is no error shown, in mapper implementation (generated code) there is no mapping for that ID:
#Override
public User userDtoToUser(UserDto userDto) {
if ( userDto == null ) {
return null;
}
UserBuilder user = User.builder();
user.name( userDto.getName() );
user.lastName( userDto.getLastName() );
user.username( userDto.getUsername() );
user.password( userDto.getPassword() );
user.profilePicturePath( userDto.getProfilePicturePath() );
return user.build();
}
I'm guessing (as you have not put buider code) the problem is that your builder class does not include parent class field. MapStruct makes some assumption while generating code for mapper. From documentation -
The default implementation of the BuilderProvider assumes the
following:
The type has a parameterless public static builder creation method
that returns a builder. So for example Person has a public static
method that returns PersonBuilder.
The builder type has a parameterless public method (build method)
that returns the type being build In our example PersonBuilder has a
method returning Person.
In case there are multiple build methods, MapStruct will look for a
method called build, if such method exists then this would be used,
otherwise a compilation error would be created.
If you are using Lombok, you can solve this by using #SuperBuilder as -
#SuperBuilder
#Getter
#ToString
public class UserDto extends BaseDto {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
#Getter
#SuperBuilder
class BaseDto {
private Long id;
}
#SuperBuilder
#Getter
#ToString
public class User extends BaseEntity {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
#Setter
#Getter
#SuperBuilder
class BaseEntity {
private Long id;
}
And generated could looks like -
#Override
public User userDtoToUser(UserDto userDto) {
if ( userDto == null ) {
return null;
}
UserBuilder<?, ?> user = User.builder();
user.id( userDto.getId() );
user.name( userDto.getName() );
user.lastName( userDto.getLastName() );
user.username( userDto.getUsername() );
user.password( userDto.getPassword() );
user.profilePicturePath( userDto.getProfilePicturePath() );
return user.build();
}
I use MongoDBRepository in spring boot, and when I save some object in database everything is ok. but when I find object by id spring does not allow do that.
I try to change VehicleRoutingProblemSolution type to Object type, but VehicleRoutingProblemSolution have other object field PickupService and it without default constructor to. And yes, this class has immutable... I can't create default constructors, what can I do?
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "vrp_solutions")
public class VrpSolutionHolder {
// Specifies the solution id
#Id
#JsonProperty("id")
private String id;
// Specifies the solution id
#JsonProperty("solution")
private VehicleRoutingProblemSolution vehicleRoutingProblemSolution;
// Created at timestamp in millis
#JsonProperty("created_at")
private Long created_at = System.currentTimeMillis();
public VrpSolutionHolder(String id, VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.id = id;
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public VehicleRoutingProblemSolution getVehicleRoutingProblemSolution() {
return vehicleRoutingProblemSolution;
}
public void setVehicleRoutingProblemSolution(VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public Long getCreated_at() {
return created_at;
}
public void setCreated_at(Long created_at) {
this.created_at = created_at;
}
}
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.data.mapping.model.MappingInstantiationException:
Failed to instantiate
com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution
using constructor NO_CONSTRUCTOR with arguments
I ran into the exact same problem. A persistent immutable class containing other class instances, throwing that aforementioned exception when retrieved by this repository method:
public interface ProjectCodeCacheRepository extends MongoRepository<CachedCode, String> {
public CachedCode findByCode(String code);
public List<CachedCode> findByClientId(UUID clientId);
}
...
List<CachedCode> cachedForClient = this.codeCacheRepo.`**findByClientId**`(clientId);
...
Following Erwin Smouts hints, this is nicely fixed by giving it a special constructor annotated org.springframework.data.annotation.PersistenceConstructor like so:
#Document(collection="cachedcodes")
public class CachedCode {
#PersistenceConstructor
public CachedCode(String code, UUID clientId, LocalDateTime expiration) {
this.code = code;
this.clientId = clientId;
this.expiration = expiration;
}
public CachedCode(String code, UUID clientId, long secondsExpiring) {
this.code = code;
this.clientId = clientId;
this.expiration = LocalDateTime.now().plusSeconds(secondsExpiring);
}
public UUID getClientId( ) {
return this.clientId;
}
public String getCode() {
return this.code;
}
public boolean hasExpired(LocalDateTime now) {
return (expiration.isBefore(now));
}
...
#Id
private final String code;
private final UUID clientId;
private final LocalDateTime expiration;
}
So, you should check if your VehicleRoutingProblemSolution has a) a constructor that matches the database fields (check in mongo client) and b) is annotated to be the one used by the driver (or whichever piece of Spring magic under the hood).
If your framework tool requires (visible) no-arg constructors (plus accompanying setters), and the class you have is required to stay as is, then you could roll your own, say, MutableVehicleRoutingProblemSolution where in the setters you could have :
this.vehicleRoutingProblemSolution = new VehicleRoutingProblemSolution(vehicleRoutingProblemSolution.getId(), newSolution);
Thus your MutableVehicleRoutingProblemSolution wraps around the existing VehicleRoutingProblemSolution.
Hacky smell to it, but it fits the requirements.
(Or you could try to find a tool that is able to use, not annotations on the contained fields, but annotations on constructor arguments.)
This is a problem where the corresponding class does not have a no-arg constructor like - I was facing an issue with java.io.File.
Solution:
In general - change the declaration to Object class and convert where we are using the class.
from
class MyClass{
File myfile;
}
to
class MyClass{
Object myFile;
}
For anyone using lombok, you need to remove the #Builder annotation on your class and use #Data instead, or follow the above solution to provide a specialized constructor
Oddly, I received this when I attempted to decorate a custom interface with ...
#Document(collection = "Person")
Example:
package test.barry.interfaces;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
#Document(collection = "Person")
public interface CustomRepository
{
void updatex(Query filterPredicate, UpdateDefinition updatePredicate);
}
I want to load any property files in spring POJO classes.
Actually, I want to use any properties from property file in #Size, #NotNull or any validation annotations in spring model classes.
But the issue is that #Size, #NotNull etc annotation are invoked at compile-time whereas any property file's data are invoked at runtime.
Even, I want to use in the max parameter of #Size which accepts the only constant integer. How can I load value and cast it to Integer as a constant value?
And I can use custom validator or any custom annotation to solve the issue but as per the organization policy, I can't use much customization.
I found an additional solution to this problem that we can use a class of constants instead of the property file.
I can use this constants file anywhere in my workspace whereas property file can't use at Size.max property of the model validation
CommonConstants.java
public class CommonConstants
{
public static final int NAME=4;
public static final int ROLE=2;
}
Employee.java
private int id;
#Size(max = CommonConstants.NAME, message = "length exceeds : name")
private String name;
#Size(max = CommonConstants.ROLE, message = "length exceeds : role")
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
EmployeeController.java
#Controller
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
private Map<Integer, Employee> emps = null;
public EmployeeController() {
emps = new HashMap<Integer, Employee>();
}
#RequestMapping(value = "/emp/save.do", method = RequestMethod.POST)
public String saveEmployeeAction(#Valid Employee employee, BindingResult bindingResult, Model model)
{
if (bindingResult.hasErrors()) {
logger.info("Returning empSave.jsp page");
return "empSave";
}
logger.info("Returning empSaveSuccess.jsp page");
model.addAttribute("emp", employee);
emps.put(employee.getId(), employee);
return "empSaveSuccess";
}
}
Output
I have entity class
public Class StudentEntity{
private int id;
private String name;
private AddressEntity address;
private ProfileEntity profile;
//getter setter
}
public Class StudentDTO{
private int id;
private String name;
private AddressDTO address;
private ProfileDTO profile;
//getter setter
}
when I use BeanUtils.copyProperties(); (from spring/apache common) it copies the id and name alone. How to copy the address and profile also?
If custom util has to be written, could you please share the snippet?
BeanUtils, cloning OR serialization would not work here as the inner data types are different. I would suggest you to set the fields of StudentDTO manually. You could use conversion constructor for AddressDTO and ProfileDTO. Copy constructor is the legal name, but since we are converting type also, better name would be a conversion constructor instead.
An example of conversion constructor in JDK is ArrayList(Collection<? extends E> c) , i.e. https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#ArrayList-java.util.Collection- which generates an ArrayList from any Collection object and copy all items from Collection object to newly created ArrayList object.
Example:
StudentEntity studentEntityObj = new StudentEntity();
studentEntityObj.setId(1);
studentEntityObj.setName("myStudent");
AddressEntity addressEntityObj = new AddressEntity();
addressEntityObj.setCity("myCity");
studentEntityObj.setAddress(addressEntityObj);
// All above lines would be taken care of already (i.e. data is filled from DB)
StudentDTO studentDTOObj = new StudentDTO();
// Call conversion constructor
AddressDTO addressDtoObj = new AddressDTO(addressEntityObj);
studentDTOObj.setAddress(addressDtoObj);
studentDTOObj.setId(studentEntityObj.getId());
studentDTOObj.setName(studentEntityObj.getName());
System.out.println(studentDTOObj.toString());
where AddressDTO (OR ProfileDTO for that matter) including a conversion constructor looks like:
public class AddressDTO {
private String city;
// Conversion constructor
public AddressDTO(AddressEntity a) {
this.city = a.getCity();
}
#Override
public String toString() {
return "AddressDTO [city=" + getCity() + "]";
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
prints
StudentDTO [id=1, name=myStudent, address=AddressDTO [city=myCity]]
You can try to use SerializationUtils.clone(). This method deep clone your object. But you should mark your objects as Serializable.
https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/SerializationUtils.html#clone(T)
I have implemented my validation for list of custom class as mention in this post. For reference here my code looks like
class TopDtoForm {
#NotEmpty
private String topVar;
private List<DownDto> downVarList;
//getter and setter
}
class DownDto {
private Long id;
private String name;
//getter and setter
}
#Component
public class TopDtoFormValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return TopDtoForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
TopDtoForm topDtoForm = (TopDtoForm) target;
for(int index=0; index<topDtoForm.getDownVarList().size(); index++) {
DownDto downDto = topDtoForm.getDownVarList().get(index);
if(downDto.getName().isEmpty()) {
errors.rejectValue("downVarList[" + index + "].name", "name.empty");
}
}
}
}
So even I send empty name binding result has 0 error. I tested with topVar and it is working fine. My question is do I have to do any other configuration to say use this validator?
Thanks
In Spring MVC just annotate in TopDtoForm your list with #Valid and add #NotEmpty to DownDto. Spring will validate it just fine:
class TopDtoForm {
#NotEmpty
private String topVar;
#Valid
private List<DownDto> downVarList;
//getter and setter
}
class DownDto {
private Long id;
#NotEmpty
private String name;
//getter and setter
}
Then in RequestMapping just:
#RequestMapping(value = "/submitForm.htm", method = RequestMethod.POST) public #ResponseBody String saveForm(#Valid #ModelAttribute("topDtoForm") TopDtoForm topDtoForm, BindingResult result) {}
Also consider switching from #NotEmpty to #NotBlank as is also checks for white characters (space, tabs etc.)