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;
}
}
I want to find a Type document by the code of job and by the list of code of category, i tried the below query but it didn't work
#Document
public class Type {
#Id
private String id;
#DBRef
private Job job;
#DBRef
private List<Category> categories;
}
public class Job {
#Id
private String id;
private String code;
}
public class Category {
#Id
private String id;
private String code;
}
public interface TypeRepository extends MongoRepository<Type, String> {
#Query("{ 'job.code': ?0, 'category.code': { $in: ?1 }}")
Type findByJobAndCategoriesCode(String codeJob, List<String> codeCategories);
}
try using this one
public interface TypeRepository extends MongoRepository<Type, String> {
Type findOneByJobCodeAndCategoriesCodeIn(String codeJob, List<String> codeCategories);
}
I'm developing a website for my company, and I use Spring as my backend.
There is a situation now, where I need to use one of my Utils method twice, but for different DAOs.
In order to avoid code duplication, I was wondering how can I use Java Generics in order to make this method usable for both cases. The method just count one of the fields which is common for both DAOs.
Util method :
SeverityCount calculateSeveritiesCount(List<?> events){
if(null == events){
return new SeverityCount();
}
if(events.get(1) instanceof EventDAO){
events = (List<EventDAO>)events;
}
else if (events.get(1) instanceof EventsByAreaDAO) {
events = (List<EventsByAreaDAO>)events;
}
Map<String, Long> severityCountMap = events.stream().collect(
Collectors.groupingBy(
EventDAO::getSeverity, //It should be EventDAO or EventsByAreaDAO. both has severity field.
Collectors.counting())
);
return mapper.convertValue(severityCountMap, SeverityCount.class);
}
Event DAO:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "events")
public class EventDAO {
#Id #Column(name = "uid")
private String uID;
private String date;
private String severity;
}
Area DAO:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "events")
public class EventsByRegionDAO {
#Id #Column(name = "uid")
private String uID;
private String date;
private String type;
private String severity;
private String area;
private String server;
}
This is how I call it from the service:
SeverityCount severitiesCount = Utils.calculateSeveritiesCount(eventsList); //EventsList could be list of EventDAO or EventsByAreaDAO
You can change the method to
SeverityCount calculateSeveritiesCount(List<? extends SeverityCalculable> events)
where SeverityCalculable
interface SeverityCalculable {
String getSeverity(); // implemente getter in all subclasses
}
Have all your relevant classes implement this interface.
public class EventDAO implements SeverityCalculable {
// ...
#Override
public String getSeverity() {
return this.severity;
}
}
Now in your method, remove the casts and it should become something like this:
SeverityCount calculateSeveritiesCount(List<? extends SeverityCalculable> events) {
if(null == events){
return new SeverityCount();
}
Map<String, Long> severityCountMap = events.stream().collect(
Collectors.groupingBy(
SeverityCalculable::getSeverity,
Collectors.counting()
)
);
return mapper.convertValue(severityCountMap, SeverityCount.class);
}
Since both DAO's have a severity property, they could potentially implement a common interface, say SeverityAware:
public interface SeverityAware {
public String getSeverity();
}
public class EventsByRegionDAO implements SeverityAware { .. }
public class EventDAO implements SeverityAware { .. }
Further, your method could now accept subtypes of this interface:
SeverityCount calculateSeveritiesCount(List<? extends SeverityAware> events){
if (null == events){
return new SeverityCount();
}
Map<String, Long> severityCountMap = events.stream().collect(Collectors.groupingBy(
SeverityAware::getSeverity,Collectors.counting())
);
return mapper.convertValue(severityCountMap, SeverityCount.class);
}
how can I properly handle an update of the base class when using inheritance for mongo entities?
Base class:
#Document
public class User {
#Id
private String id;
#UniqueElements
protected String uid;
protected String photoUrl;
protected Address homeAddress;
protected Gender gender;
protected List<Kid> kids;
protected GeneralInfo generalInfo;
protected List<Address> addresses;
protected String instanceId;
public User() {
}
public User(User user) {
this.uid = user.getUid();
this.photoUrl = user.getPhotoUrl();
this.homeAddress = user.getHomeAddress();
this.gender = user.getGender();
this.kids = user.getKids();
this.generalInfo = user.getGeneralInfo();
this.addresses = user.getAddresses();
this.instanceId = user.getInstanceId();
}
}
Children:
#Document
public class Sitter extends User{
private int childCareExperienceYrs;
private AgeRange preferredKidsAgeRange;
private List<Skill> skills;
private String talents;
private LivingSpace livingSpace;
#JsonProperty
private boolean pets;
#JsonProperty
private boolean hasCar;
#JsonProperty(value = "available")
private boolean available;
}
#Document
public class Parent extends User{
}
The properties inherited from User are saved in the children documents.
When I update a User property the child tables are not updated.
For example, when changing the user address, the 'Sitter' document will still have the old address.
How to make any change from the base class to reflect on child classes too?
Does this situation require an update query for each child when the base class is changed?
I have two classes Employee and Address.
public class Employee implements java.io.Serializable {
private long id;
private String name;
private Address address;
private UserType userType;
private Date bdate;
public Employee() {
}
// getters and setters
}
public class Address implements java.io.Serializable {
private long id;
private Set<String> lines;
public Address() {
}
// getters and setters
}
I am using moxy to convert hibernate entities to json. I have enabled it in pom.xml. Also added #XmlRootElement on the top of entity. Below is my service code.
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("{/Id}")
public Employee test(#PathParam("Id") long Id)
{
return EmployeeDao.getEmployee(Id);
}
Right now I am getting 500 for this service. Any help will be appreciated.