1

Recently, I started a new job (4 months now) and I still have a lot to learn from Spring. One of the issues we're having is that when we bring objects from the DB and map them into (Repository -> Service -> Controller) a JSON, it brings a lot of unnecessary data - For example (small example):

We have an Employee class which has a Department object and it brings the following:

{
   'EmployeeId':1,
   'Name':'John',
   'Email':'email@moose.com'
   'Department': {
            'DepartmentId':1,
            'name':'Development',
            'location':'Somewhere',
   }
}

Class details:

@Entity
@Table(name = "Employees")
@NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employees e")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "EmployeeId")
    private long EmployeeId;

    @Column(name = "Email")
    private String email;

    @Column(name = "address")
    private String address;

    @ManyToOne
    @JoinColumn(name = "DepartmentId")
    @JsonBackReference(value = "department_employee")
    private Departament departament;

    // Other attributes, setters & getters

}

If I want to build a "custom" mapper, which is the best practice? Or how can I implement something that allows me to create a JSON that brings me something like this:

{
   'EmployeeId':'1',
   'Name':'John',
   'Email':'email@moose.com'
}

I've been doing some research and I stumbled across the following examples:

Jackson JSON Views

Jackson's @JsonView, @JsonFilter and Spring

Also I've been thinking about using a generic object that contains a HashMap so I can include, on the Controller, the data that I want or need for an specific screen.

I really appreciate any help - hope the question is clear enough.

Community
  • 1
  • 1
Walter G.
  • 135
  • 4
  • 12
  • btw, question was also posted on: https://coderanch.com/t/669152/Spring/Spring-Mapping-Objects#3124303 – Walter G. Aug 11 '16 at 20:52
  • If you don't want to serialize the department you can use @JsonIgnoreProperties annotation. – Azizi Aug 11 '16 at 20:53
  • My apologies if I didn't specify that on the question - on some screens, I need the department, for example when I need to update the Employee. But, If I just need to display just the name, email and some specific data, which is the more appropriate way of doing it? The @JsonIgnoreProperties ignores the field completely. – Walter G. Aug 11 '16 at 20:59
  • Maybe my old answer to a similar question will be helpfull http://stackoverflow.com/a/30645468/1847202 – chimmi Aug 12 '16 at 07:38

2 Answers2

1

One library that works particularly well for me is Model Mapper. The way I use it is by having your Domain object (like you do now) and a DTO. So in your case, Employee and EmployeeDTO. This allows for clear separation between the layers of your app, and mapping between employee and employeeDto with model mapper is quite easy, just modelMapper(employee, EmployeeDTO.class) for instance. All you gotta do for the simple cases is name the properties with the same name in the domain class and the dto class, and the library will take care of copying the values. For more complicated cases it has several alternatives that will still make your code clean.

Ulises
  • 9,115
  • 2
  • 30
  • 27
0

Say for example you have a JPA entity Comment (which captures a comment the user makes) and you wanted to customize the serialization (converting from object -> JSON)

Define the entity

//Comment Entity Attributes (This is just an example make sure you properly annotate the entity etc...):
private String id;
private final String userId;
private  String discussionId;
private final Date createdTime;
private final String comment;

Create a custom serializer

/*Serializer*/
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

@Component
@Slf4j
public class CommentSerializer extends JsonSerializer<Comment> {


    @Override
    public void serialize(Comment comment, JsonGenerator jgen, SerializerProvider provider)
            throws IOException,
            JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeStringField("id", comment.getId());
        jgen.writeStringField("userId", comment.getUserId());
        jgen.writeStringField("firstName", redisRepo.getName(comment.getUserId(), false));
        jgen.writeStringField("discussionId", comment.getDiscussionId());
        jgen.writeStringField("createdTime", String.valueOf(comment.getCreatedTime().getTime()));
        jgen.writeStringField("comment", comment.getComment());
        jgen.writeEndObject();
    }


}

Configure the serializer

/*Config*/
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;

@Autowired
private CommentSerializer commentSerializer;

@Bean
public HttpMessageConverters httpMessageConverters() {
    return new HttpMessageConverters(mappingJackson2HttpMessageConverter());
}

   @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2JsonView = new MappingJackson2HttpMessageConverter();
        mappingJackson2JsonView.setObjectMapper(objectMapper());
        return mappingJackson2JsonView;
    }


   @Bean
    public ObjectMapper objectMapper() {



        Map<Class<?>, JsonSerializer<?>> serializerMap = new HashMap<>();
        serializerMap.put(Comment.class, commentSerializer);

        Jackson2ObjectMapperFactoryBean objectMapperFactoryBean = new Jackson2ObjectMapperFactoryBean();
        objectMapperFactoryBean.setIndentOutput(true);

        objectMapperFactoryBean.setSerializersByType(serializerMap);
        objectMapperFactoryBean.afterPropertiesSet();

        ObjectMapper objectMapper = objectMapperFactoryBean.getObject();
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.findAndRegisterModules();
        objectMapper.addMixInAnnotations(OAuth2Exception.class, OAuth2ExceptionMixin.class);

        return objectMapper;
    }

Now whenever the relevant url/endpoint is called (like the one below) it will always serialize according the logic define in the CommentSerializer)

    @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Comment findOne(@PathVariable("id") String id)
        ....
    return comment;
}
Shivam Sinha
  • 4,924
  • 7
  • 43
  • 65