Jackson and multiple interface inheritance - java

I'm trying to implement json serialization of the single entity to different views according to used interface.
For example we have:
public interface BookBrief {
long getId();
String getTitle();
}
public interface BookPreview {
long getId();
String getAnnotation();
}
public class Book implements BookBrief, BookPreview {
// class fields here
public long getId() {...}
public String getTitle() {...}
public String getText() {...}
public String getAnnotation() {...}
// setters here
}
// service which results is serialized to json in Spring MVC controllers
public interface BookService {
List<? extends BookBrief> getBooks();
BookPreview getBookPreview(long id);
Book getBook(long id);
}
BookService implementation always returns Book class (with unused fields set to null).
To serialize interfaces I tried to use annotation #JsonSerialize(as = Interface.class) for each,
but for all interfaces jackson always use only the first one listed in 'implements' expression.
Is there a way to configure jackson like I need? Or may be there is a better solution?

Seems like you have 2 options:
Write a custom Jackson Serializer
Use Jackson views, which looks like a more viable choice (full documentation could be found here).
With Views it could be implemented in 3 easy steps:
Define your view markers:
BookViews.java:
public class BookViews {
public static class BookBrief { }
public static class BookPreview { }
}
Annotate which Book fields you want to be exposed in each view:
Book.java:
public class Book {
#JsonView({BookViews.BookBrief.class, BookViews.BookPreview.class})
private long id;
#JsonView(BookViews.BookBrief.class)
private String title;
#JsonView(BookViews.BookPreview.class)
private String annotation;
// Constructors and getters/setters
}
Annotate REST method with JSonValue and specify which view you want to use:
BookService.java:
#Path("books")
public class BookService {
private static final List<Book> library = Arrays.asList(
new Book(1, "War and Peace", "Novel"),
new Book(2, "A Game of Thrones", "Fantasy")
);
#GET
#Path("all")
#JsonView(BookViews.BookBrief.class)
#Produces(MediaType.APPLICATION_JSON)
public Response getBooks() {
return Response.status(Response.Status.OK).entity(library).build();
}
#GET
#Path("previews")
#JsonView(BookViews.BookPreview.class)
#Produces(MediaType.APPLICATION_JSON)
public Response getBookPreviews() {
return Response.status(Response.Status.OK).entity(library).build();
}
}
Result:
GET http://localhost:8080/root/rest/books/all:
[
{
"id": 1,
"title": "War and Peace"
},
{
"id": 2,
"title": "A Game of Thrones"
}
]
GET http://localhost:8080/root/rest/books/previews:
[
{
"annotation": "Novel",
"id": 1
},
{
"annotation": "Fantasy",
"id": 2
}
]

Related

Java Mapstruct generic enum converter

I have two different enum classes and one of them use them as a value like
public enum Type{
TEST1{
#Override
public Type convertToINT() {
return Type.INTEGRATION1;
}
},
TEST2{
#Override
public Type convertToINT() {
return Type.INTEGRATION2;
}
},
TEST3{
#Override
public TypeIntegration convertToINT(){
return Type.INTEGRATION3;
}
};
public abstract TypeIntegration convertToINT();
}
public enum TypeIntegration {
INTEGRATION1,
INTEGRATION2,
INTEGRATION3
}
This enums uses in different classes for example ;
#Getter
#Setter
public class typeSaveReqDto{
private blabla;
private blabla;
private Type type;
}
#Getter
#Setter
public class typeIntegrationObject{
private blabla;
private blabla
private TypeIntegration type;
}
I want to use mapstructs and convert via auto generated classes, but mapstruct throws me exception like "The following constants from the property "Type type" enum have no corresponding constant in the "TypeIntegration type" enum and must be be mapped via adding additional mappings: TEST1, TEST2, TEST3"
I want to create EnumMapper classes for converting enums, How can i write generic classes for this with mapStructs java ?
Edit :
I generated EnumMapper
#Mapper
public class EnumMapper {
EnumMapper INSTANCE = Mappers.getMapper(EnumMapper.class);
#Named("enumToIntEnum")
public static <T extends EnumConverter<INT>,INT> INT convertToINT(T enums, #TargetType INT enumClass){
INT convertObj = ((T)enums).convertToINT();
return convertObj;
}
}
And Mapper interfaces like below
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type",qualifiedByName = "enumToIntEnum")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
But I got a fail like 'Can't map property "Type type" to "TypeIntegration type". Consider to declare/implement a mapping method: "TypeIntegration map(Type value)".'
Type T isn't know and doesn't contain a convertToINT method as it can be anything. So keep it simple and just write a dedicated mapper instead of trying to build one that does everything.
#Mapper(componentModel="spring")
public class TypeToTypeIntegrationMapper {
#Mapping
public TypeIntegration map(Type from) {
return from.convertToINT();
}
}
Don't make it more complex.
You could even ditch the mapper and write an expression instead.
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type", expression = "java(type.convertToINT())")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
MapStruct will now use the expression to generate the mapping code instead of you having to write a mapper yourself.

Spring Java POST endpoint that can have a field be different data types

I'm working on a Java Spring Boot HTTP Service application. I currently have a POST endpoint that I have defined inside of a #RestController. This controller, called processRequest takes an object called Info with the #RequestBody annotation.
Right now, I have it setup where a user can send JSON based on the Info class that I defined like this:
//Sample JSON Payload
{
"name": "Bob",
"age": 26,
"hobby": biking
}
//Sample Object
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
public final String hobby
}
What I want to do know is respond to the situation where one of the fields is sent as a different datatype. For example:
//JSON payload with different datatype for a field
{
"name": "Bob",
age: 26,
"hobby": ["biking", "hiking"] //This is supposed to be a string but it's an array.
}
Is it possible to keep the endpoint properties the same but handle different data types? Maybe I can create another class where the fields are different and spring will automatically create the one that matches the input? I'm curious for what the best approach to this problem would be.
In this particular example, where the hobby could either be a single value or multiple values, I would rely on the Jackson ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
This can be configured application-wide within application.properties:
spring.jackson.deserialization.accept-single-value-as-array=true
Or this can be enabled for a specific field:
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
public final List<String> hobby
}
For more complex cases, Jackson recommends that you write a wrapper with a specific type field to provide a hint of which type it should deserialize. For example:
public class InfoWrapper {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = PersonInfo.class, name = "person")
})
private Info info;
}
public interface Info {}
#RequiredArgsConstructor
public class PersonInfo implements Info {
public final String name;
public final int age;
public final String hobby
}
So, if you want to send a JSON containing PersonInfo, you can use the following JSON:
{
"type": "person",
"info": {
"name": "Bob",
"age": 26,
"hobby": "biking"
}
}
If you need more advanced control over what you want to do, you can write a custom deserializer and apply it with the #JsonDeserialize annotation.
You can use JsonNode for the field which changes. As below:
public class Info {
public String name;
public int age;
public JsonNode hobby;
#Schema(description = "")
#Valid
#JsonIgnore
public List<String> getHobbies() {
// if hobby is array create Java object and return
// else if hobby is String create List, add the string to it and return it.
// or some other solution of your choice and requirement
}
}

How to get a specific attribute an object in JSON?

I working with JAX-RS and I want to get a JSON Object from my resource.
For example, I have the next code:
Book class:
#XmlRootElement
public class Book {
private int id;
private String name;
}
And person class:
#XmlRootElement
private class Person {
private int id;
#XmlElement(name="full_name")
private String fullName;
#XmlElement(name="book_id")
private Book book;
}
I want get this:
{
"id": 1,
"full_name": "Gustavo Pacheco",
"book_id": 8
}
And don't get this:
{
"id": 1,
"full_name": "Gustavo Pacheco",
"book": {
"id": 8,
"name": "Cien AƱos De Soledad"
}
}
How can I get only id attribute from book class for get a more simple JSON?
The best thing for these cases is to have the classes corresponding to the DTOs and entities corresponding to the database model independently.
For example:
package com.github.ryctabo.example.entity;
/* Entity class */
#Entity
public class Person implements Serializable {}
package com.github.ryctabo.example.dto;
/* Data Transfer Object class */
#XmlRootElement
public class PersonData {}
This ensures the integrity of the database model, and independently you have how you are going to display the data in a different class.

Jackson serializes strange output

I am using Jackson to convert json to an object. However, the json looks wrong. Here is what I am seeing:
"interfaces": {"interfaces": [
"HA_1",
"HA_2"
]},
There should not be two interfaces. I want to see:
"interfaces": [
"HA_1",
"HA_2"
]},
I am not sure how this is happening. I can show you my conversion classes:
#XmlAccessorType(XmlAccessType.NONE)
public class InterfacesRep implements Serializable {
private static final long serialVersionUID = -1503363608473342020L;
#XmlElement(name = "interface", type = String.class)
private Collection<String> all = new ArrayList<String>();
public InterfacesRep() {}
public InterfacesRep(Collection<String> all) {
this.all = all;
}
public Collection<String> getAll() {
return all;
}
public void setAll(List<String> all) {
this.all = all;
}
}
And the outer class:
public class OuterRep {
private static final long serialVersionUID = -1719378545790376294L;
#XmlElement(name = "interfaces", type=InterfacesRep.class)
private InterfacesRep interfaces;
public OuterRep() {
}
public InterfacesRep getInterfaces() {
return interfaces;
}
public void setInterfaces(InterfacesRep interfaces) {
this.interfaces = interfaces;
}
}
Do you know why I see "interfaces" twice?
Because you are defining it on the property at both levels.The outer class has a property name called "interfaces" and the inner class's Collection is also named "interfaces".
This simplest fix (in my mind) would be to not use a wrapper class for the Collection. Just put the collection in the outer class.
On a side note, why are you using Jackson's XML annotations to serialize JSON?

jackson generating an unexpected "new" field

I'm using spring rest and jackson to generate json. For the class country
public class Country extends AbstractPersistable<Long> {
private String name;
private String code2;
private String code3;
public Country() {
}
public Country(String name, String code2, String code3) {
...
}
...
}
I get, for example,
{
"id" : 1,
"name" : "Afghanistan",
"code2" : "AF",
"code3" : "AFG",
**"new" : false**
}
For some classes I get an unexpected "new" field always set to false. I suspect it has something to do with the parametrized constructor, but it's just a guess. Ideas?
The class AbstractPersistable has a public method called isNew specified by the interface Persistable (the doc here).
You will have to ignore such property if you don't want it in your JSON, for example, using the annotation JsonIgnoreProperties in your class.

Categories