Spring Boot Entity to DTO ConverterNotFoundException - java

I have a simple API, built with Spring Boot, where I am trying to convert entity classes to the corresponding dtos.
Student Entity
#Entity(name = "students")
#AllArgsConstructor
#Getter
#Setter
public class Student extends AbstractUpdatable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private Long id;
#Column(name = "student_first_name", nullable = false)
#NotNull(message = "First name is mandatory")
private String firstName;
#Column(name = "student_last_name", nullable = false)
#NotNull(message = "Last name is mandatory")
private String lastName;
#Column(name = "student_ssn", unique = true, nullable = false)
#NotNull(message = "SSN is mandatory")
#Size(min = 10, max = 10)
private String ssn;
#Column(name = "student_age")
#Min(5)
#Max(100)
private Integer studentAge;
#Column(name = "student_email")
#Email
private String email;
#Column(name = "student_level")
private Integer studentLevel; // TODO could be enum or separate entity
#Column(name = "student_creation_date")
private Date creationDate; // TODO check spring's feature to manage creation and update dates (auditing)
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
public Student() {
this.creationDate = new java.util.Date(); // TODO this will be removed when spring's auditing is utilized
}
}
Group Entity
#Entity(name = "groups")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Group extends AbstractUpdatable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long id;
#Column(name = "group_name")
private String Name;
#OneToMany(mappedBy = "group")
#JsonIgnore
private List<Student> students;
}
Student DTO
public class StudentDto extends AbstractStudentDto implements Serializable {
private final Long id;
private final Date creationDate;
public StudentDto(String firstName, String lastName, String email, Long id, GroupDto group, Integer studentAge, Integer studentLevel,
Date creationDate) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.group = group;
this.studentAge = studentAge;
this.studentLevel = studentLevel;
this.creationDate = creationDate;
}
public Long getId() {
return id;
}
public Date getCreationDate() {
return creationDate;
}
}
Group DTO
public class GroupDto extends AbstractGroupDto{
private final Long id;
public GroupDto(Long id, String name, List<StudentDto> students) {
this.id = id;
this.name = name;
this.students = students;
}
public Long getId() {
return id;
}
}
GroupToGroupDtoConverter
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
private final ConversionService conversionService;
#Autowired
public GroupToGroupDtoConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
#Override
public GroupDto convert(Group source) {
var convertedStudents = new ArrayList<StudentDto>();
if (!CollectionUtils.isEmpty(source.getStudents())) {
source.getStudents().forEach(student ->
convertedStudents.add(conversionService
.convert(student, StudentDto.class)));
}
return new GroupDto(source.getId(), source.getName(), convertedStudents);
}
}
And a very similar StudentToStudentDtoConverter.
The issue is that when the code needs to do the conversion from any of the entities to their dtos I get
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[com.studentmanagement.model.Group] to type
[com.studentmanagement.dto.group.GroupDto]
Now if I try to remove the conversion of the students' list to a list of student dtos in the converter above, so the converter looks like this:
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
#Override
public GroupDto convert(Group source) {
return new GroupDto(source.getId(), source.getName(), new ArrayList<StudentDto>());
}
}
The conversion works with no issues (with a dummy students list of course). Am I missing something when I am adding the conversion service inside my converters?

I tried to replicate the issue and did a small working demo on this.
One thing I found while doing that, that could be relevant to your case, is that injecting a conversion service into a converter is not trivial (see e.g. this and this relevant issues).
Also, important as well, do not forget to register the converters, as shown below on the code samples, and e.g. here. From the error message you posted, seems like the service cannot find the needed converter.
For my demo, please note I removed the group field from the StudentDTO class to simplify things. I hope it helps, happy to share the full code as well in github.
I used the following converter for Group:
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
private ConversionService conversionService;
public GroupToGroupDtoConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
#Override
public GroupDto convert(Group group) {
List<StudentDto> studentDtoList =
group.getStudents().stream().map(a -> conversionService.convert(a, StudentDto.class)).collect(Collectors.toList());
GroupDto groupDto = new GroupDto(group.getId(), group.getName(), studentDtoList);
return groupDto;
}
}
But in order to successfully inject the conversion service and register the converters I added this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
#Lazy
ConversionService conversionService;
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new GroupToGroupDtoConverter(conversionService));
registry.addConverter(new StudentToStudentDtoConvervter());
}
}
If, for example, I comment out the first addConverter line, I get the Converter not found exception:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.conversion_entities.Group] to type [com.example.conversion_entities.GroupDto]
Once all is done, the following test passes:
#RunWith(SpringRunner.class)
#SpringBootTest
class GroupToGroupDtoConverterTest {
#Autowired
ConversionService conversionService;
#Test
void convert() {
Student studentA = new Student();
studentA.setFirstName("John");
studentA.setLastName("Doe");
Student studentB = new Student();
studentB.setFirstName("Jane");
studentB.setLastName("Doe");
List<Student> studentList = new ArrayList<>();
studentList.add(studentA);
studentList.add(studentB);
Group group = new Group(1L, "groupA", studentList);
GroupDto convertedGroupDto = conversionService.convert(group, GroupDto.class);
assertEquals("John", convertedGroupDto.getStudents().get(0).getFirstName());
assertEquals("Jane", convertedGroupDto.getStudents().get(1).getFirstName());
}
}

I think the problem is ConversionService can't convert your classes by default. Instead try to inject a class implementing ConversionService with correct convert method implementation inside.

Related

500 Internal Server Error in spring boot post API

I'm trying to create a post API in a spring boot app but it's not working and i can't find the issue in the code
This is service:
#Transactional
public Invoice saveInvoice(Invoice invoice) {
Invoice newInvoice = invoiceRepository.save(invoice);
return newInvoice;
}
This is the controller:
#PostMapping("/save")
public ResponseEntity<Invoice> addInvoice(#RequestBody InvoiceDTO invoice) {
try {
Invoice newInvoice = invoiceService
.saveInvoice(new Invoice(invoice.getSerialNumber(), invoice.getStatus(), invoice.getCreatedDate()));
return new ResponseEntity<>(newInvoice, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
and this is the model :
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
#ManyToOne
#JoinColumn(name = "employee_id")
private Employee employee;
#OneToMany
private Set<InvoiceHistory> invoiceHistories;
#ManyToMany
private Set<Item> items;
public Invoice(long serialNumber, String status, Timestamp createdDate) {
this.serialNumber = serialNumber;
this.status = status;
this.createdDate = createdDate;
this.isDeleted = false;
}
}
But when I am running in in postman its returning 500 Internal Server Error
Update error message :
, message=Cannot invoke
"com.example.invoices.repository.IInvoiceRepository.save(Object)"
because "this.invoiceRepository" is null, path=/invoice/save}]
where's the problem ?
For your service class, how are you declaring and creating invoiceRepository? If it's like this:
private InvoiceRepository invoiceRepository;
Then you need to add an #Autowired:
#Autowired
private InvoiceRepository invoiceRepository;
If you do have it #Autowired, make sure your service class is annotated with #Service:
#Service
public class InvoiceService {...}
If you have that, make sure you aren't creating InvoiceService with "new" in your controller. Instead of:
private InvoiceService invoiceService = new InvoiceService();
or "new"ing it in a constructor,
use #Autowire
#Autowire
private InvoiceService invoiceService;

How to get object from OneToMany collection of objects?

I have an Order entity and OrderProduct. I want to show order details on frontend and of course order products in it. So how to fetch product object in OrderProduct JSON. I'm missing product object in products array. I don't need order object one more time and i think it going to be a infinite recursion stuff with it. :)
My Order entity:
#Entity
#Getter
#Setter
#Table(name ="orders")
public class Order{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
private BigDecimal totalPrice;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonManagedReference(value="orders")
private List<OrderProduct> products = new ArrayList<>();
private int userId;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date date = new Date();
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date deliveryDate;
#Enumerated(EnumType.STRING)
private OrderType orderType;
}
My OrderProduct entity:
#Entity
#Setter
#Getter
public class OrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JsonBackReference(value="product")
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JsonBackReference(value="orders")
#JoinColumn(name = "order_id")
private Order order;
private Integer quantity;
}
Product entity:
#Entity
#Getter
#Setter
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
private double price;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
#JsonManagedReference(value="ingredients")
private List<Ingredient> ingredients = new ArrayList<>();
#OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
#JsonManagedReference(value="product")
private List<OrderProduct> products = new ArrayList<>();
private String fileName;
}
This can help annotate one of your entity clases with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
You can use #JsonViewannotation to define the fields that you need to serialize to JSON
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(<some interface.class>)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
In your case i think you should annotate all the fields you need except:
#OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
#JsonManagedReference(value="product")
private List<OrderProduct> products = new ArrayList<>();
in Product
to avoid circular serialization
Or as alternative you can just use DTO classes or seralize oject to JSON manualy (https://thepracticaldeveloper.com/java-and-json-jackson-serialization-with-objectmapper/)
This can be done by my library beanknife
// This configure generate a class named ProductInfo which has the same shape with Product without property "products"
#ViewOf(value = Product.class, genName="ProductInfo", includePattern = ".*", excludes = {"products"})
class ProductInfoConfigure {}
// This configure generate a class named OrderProductRelation with the same shape of OrderProduct.
// But it has not order property and the type of its product property is change to ProductInfo generated above.
#ViewOf(value = OrderProduct.class, genName="OrderProductRelation", includePattern = ".*", excludes = {"order"})
class OrderProductRelationConfigure {
#OverrideViewProperty("product")
private ProductInfo product;
}
// This configure generate a class named OrderDetail with the same shape of Order.
// But the type of its products property is change to List<OrderProductRelation>
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
}
will generate these classes:
class ProductInfo {
private Long id;
private String name;
private double price;
private List<Ingredient> ingredients; // it is not processed because you have not provide the class Ingredient
private String fileName;
}
public class OrderProductRelation {
private Long id;
private ProductInfo product;
private Integer quantity;
}
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
private Date date = new Date();
private Date deliveryDate;
private OrderType orderType;
}
Then
Order order = ...
OrderDetail orderDetail = OrderDetail.read(order);
// serialize the otherDetail instead of order.
List<Order> orders = ...
List<OrderDetail> orderDetails = OrderDetail.read(orders);
// serialize the orderDetails instead of orders.
Possible problems:
I doesn't use Lombok, so Lombok may need to be adapted because it change the byte code on the fly. But it is not a big problem, I will try to adapt it if someone commit the issue and provide enough use cases.
The generated class does not inherit the annotation on the original class. In next release I will provide a sulotion. At this moment, as a workaround, we can use custom method to convert the property manually. such as
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
#OverrideViewProperty("orderType")
public static String orderType(Order source) {
return source.getOrder().name();
}
}
The generated class will be changed to
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
private Date date = new Date();
private Date deliveryDate;
private String orderType;
}
Update
Version 1.2.0 released. Add support of annotation inheritance.
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
#UseAnnotation({DateTimeFormat.class, Enumerated.class, JsonProperty.class})
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
}
generate
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date date;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date deliveryDate;
#Enumerated(EnumType.STRING)
private OrderType orderType;
}

Getting nulls while using ModelMapper

I'm trying to utilize the ModelMapper in my convertion process. What I need to do is to convert the Sample entity to SampleDTO object.
I have the Sample entity like the following:
#Entity
#Table(name = "sample", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Sample {
private static final String SEQUENCE = "SAMPLE_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "name")
private String name;
#Column
private String surname;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id_deetails")
private Details details;
}
Which holds the Details one:
#Entity
#Table(name = "details", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Details {
private static final String SEQUENCE = "DETAILS_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "street_name")
private String streetName;
#Column
private String city;
}
I'd like the DTO to be this format:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class SampleDTO {
private Long id;
private String name;
private String surname;
private String streetName;
private String city;
}
I also made a ModelMapper bean like:
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
And I made a converter component:
#Component
public class EntityDtoConverter {
private final ModelMapper modelMapper;
#Autowired
public EntityDtoConverter(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public SampleDTO sampleToDto(Sample entity) {
return modelMapper.map(entity, SampleDTO.class);
}
}
The problem is
when I try to use this mapper converter in my service
#Service
public class SampleService {
private final SampleRepository sampleRepository;
private final EntityDtoConverter entityDtoConverter;
#Autowired
public SampleService(SampleRepository sampleRepository, EntityDtoConverter entityDtoConverter) {
this.sampleRepository = sampleRepository;
this.entityDtoConverter = entityDtoConverter;
}
public List<SampleDTO> getSamples() {
List<SampleDTO> samples = sampleRepository.findAll()
.map(entityDtoConverter::sampleToDto);
return new List<SampleDTO>(samplesPage);
}
}
I get nulls in places of Details fields.
I have followed Baeldung's tutorial about model-to-dto conversion with ModelMapper and the documentation of it as well but the least wasn't much of help. There is something I'm missing and I have no idea what it is.
I'm working on:
Java 11
Spring Boot 2.3.0
ModelMapper 2.3.8
Try:
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
Also check: Modelmapper: How to apply custom mapping when source object is null?

Unable to locate Attribute with the given name - Spring Data JPA Projections, Hibernate and BaseEntity

I am mapping Entities in Hibernate with JPA and Spring Data and when I run application I get
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [id] on this ManagedType [p.s.t..entity.BaseEntity]
at org.hibernate.metamodel.internal.AbstractManagedType.checkNotNull(AbstractManagedType.java:128) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.hibernate.metamodel.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:113) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.hibernate.metamodel.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:111) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:633) ~[spring-data-jpa-2.1.11.RELEASE.jar:2.1.11.RELEASE]
at org.springframework.data.jpa.repository.query.JpaQueryCreator.complete(JpaQueryCreator.java:175) ~[spring-data-jpa-2.1.11.RELEASE.jar:2.1.11.RELEASE]
I have a superclass BaseEntity:
#MappedSuperclass
#Getter
#Setter
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue
private Long Id;
private String uuid = UUID.randomUUID().toString();
#Override
public boolean equals(Object that) {
return this == that ||
that instanceof BaseEntity && Objects.equals(uuid, ((BaseEntity) that).uuid);
}
#Override
public int hashCode() {
return Objects.hash(uuid);
}
}
Regular class Task, which extends the BaseClass
#Getter
#Setter
#Table(name = "task")
#Entity
#NoArgsConstructor
#NamedEntityGraph(
name = "Task.detail",
attributeNodes = {
#NamedAttributeNode("attachments"),
#NamedAttributeNode("tags")
}
)
public class Task extends BaseEntity {
private String title;
private String description;
private LocalDateTime createdAt;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "task_id")
private Set<Attachment> attachments = new HashSet<>();
#ManyToMany
#JoinTable(
name = "tags_tasks",
joinColumns = #JoinColumn(name = "task_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
public Task(String title, String description, LocalDateTime createdAt) {
this.title = title;
this.description = description;
this.createdAt = createdAt;
}
public void addAttachment(String filename, String comment) {
attachments.add(new Attachment(filename, comment));
}
public Set<Attachment> getAttachments() {
return attachments;
}
public void addTag(Tag tag) {
tags.add(tag);
}
public void removeTag(Tag tag) {
tags.remove(tag);
}
}
TaskView for JPA query projection:
public interface TaskView {
Long getId();
String getUuid();
String getTitle();
String getDescription();
LocalDateTime getCreatedAt();
}
And JpaRepository interface:
interface TasksCrudRepository extends JpaRepository<Task, Long> {
#EntityGraph(value = "Task.detail", type = EntityGraphType.LOAD)
List<Task> findAll();
List<TaskView> findAllProjectedBy();
}
The last method - findAllProjectedBy() - in the TaskCrudRepository causes the exception pasted at the begnining of this post.
When I remove getId() method from TaskView it starts, but then I am not able to display the id of the Task in the projection.
So the question is what I am missing in this whole classes structure?
I am using:
Spring Boot 2.1.9.RELEASE
Java 11
Hibernate Core 5.3.12.FINAL
JPA 2.2
There is a typo in BaseEntity when defining ID field. Should be camelcase id instead of Id.
#MappedSuperclass
#Getter
#Setter
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue
private Long id;
private String uuid = UUID.randomUUID().toString();
#Override
public boolean equals(Object that) {
return this == that ||
that instanceof BaseEntity && Objects.equals(uuid, ((BaseEntity) that).uuid);
}
#Override
public int hashCode() {
return Objects.hash(uuid);
}
}

Java JSON - Override #JsonIgnore for tests

I have project in Spring Boot. I have User model, what have Profile model in relation OneToOne:
User: (Simplified)
#Entity
#Table(name = "users")
public class User extends AbstractEntity {
#Id
#GeneratedValue
private Integer id;
#NotEmpty
#Basic(optional = false)
#Column(nullable = false, unique = true)
private String username;
#Valid
#JsonIgnore
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
private Profile profile;
#JsonIgnore
public Profile getProfile() {
return profile;
}
#JsonProperty
public void setProfile(Profile profile) {
this.profile = profile;
}
}
Profile: (Simplified)
#Entity
#Table(name = "profiles")
public class Profile extends AbstractEntity {
#Id
#GeneratedValue
private Integer id;
#NotEmpty
#Basic(optional = false)
#Column(nullable = false)
private String name;
#NotEmpty
#Basic(optional = false)
#Column(nullable = false)
private String surname;
// Getters, setters, etc
}
My test:
#Test
public void createUserAndProfileReturnsCreatedStatus() throws Exception {
final User user = Generator.generateUser();
user.setProfile(Generator.generateProfile());
MvcResult mvcResult = this.mockMvc.perform(
post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(toJson(user)))
.andExpect(status().isCreated())
.andReturn();
}
Problem is, when i do user.setProfile(), Profile is set into User but when i call toJson(user) its automatically ignored because of my annotations in model.
How to disable those annotations just for purpose of testing? Is it possible?
I dont want to remove #JsonIgnore annotations from model, because they are there to not expose Profile when I READ user by GET /users/<id>.
This can be achieved by utilizing Jackson's Mixin feature, where you create another class that cancels the ignore annotation. The only requirement from the mixin class is to have the same property name and type. class name is not important, nor do you need to instantiate it:
public class DoNotIgnoreProfile
{
#JsonIgnore(false)
private Profile profile;
}
a Jackson Module is required to tie the bean and mixin together:
#SuppressWarnings("serial")
public class DoNotIgnoreProfileModule extends SimpleModule
{
public DoNotIgnoreProfileModule() {
super("DoNotIgnoreProfileModule");
}
#Override
public void setupModule(SetupContext context)
{
context.setMixInAnnotations(User.class, DoNotIgnoreProfile.class);
}
}
now you need to register the module into an ObjectMapper and you're all set:
public string toJson(User user)
{
try {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new DoNotIgnoreProfileModule());
return mapper.writeValueAsString(user);
} catch (IOException e) {
e.printStackTrace();
}
}
EDIT:
I just saw that ObjectMapper has an addMixin() method so the whole module setup can be skipped

Categories