Search code examples
springspring-bootjacksonjsonserializer

Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called


I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):

EndPoints example:

@GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
    try {
        List<Event> events = (List<Event>) eventsRepository.findAll();
        return new ResponseEntity<List<Event>>(
                events, HttpStatus.OK);
    }catch(IllegalArgumentException e) {
        return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
    }
}

@GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(@PathVariable String code){
    Optional<Event> event = eventsRepository.findByCode(code);
    return event.isPresent() ? 
            new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
            new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}

Serializer (class that extends of StdSerializer):

@Override
public void serialize(Event value, JsonGenerator gen, 
        SerializerProvider provider) throws IOException {

    if(firstRepresentation) {
        //First Representation
        gen.writeStartObject();
        gen.writeNumberField("id", value.getId());
        gen.writeObjectField("creation", value.getCreation());

        gen.writeObjectFieldStart("event_tracks");
        for (EventTrack eventTrack : value.getEventsTracks()) {

            gen.writeNumberField("id", eventTrack.getId());
            gen.writeObjectField("startTime", eventTrack.getStartTime());
            gen.writeObjectField("endTime", eventTrack.getEndTime());
            gen.writeNumberField("priority", eventTrack.getPriority());

            gen.writeObjectFieldStart("user");
            gen.writeNumberField("id", eventTrack.getUser().getId());
            gen.writeEndObject();

            gen.writeObjectFieldStart("state");
            gen.writeNumberField("id", eventTrack.getState().getId());
            gen.writeStringField("name", eventTrack.getState().getName());
            gen.writeEndObject();

        }

        gen.writeEndObject();
        gen.writeEndObject();
    }else if(secondRepresentation) {
       //Second Representation
    }
}

Entity:

@JsonSerialize(using = EventSerializer.class)
@RequiredArgsConstructor
@Getter
@Setter
public class Event implements Comparable<Event>{

    private Long id;

    @JsonIgnore
    private String code;

    private Timestamp creation;

    @NonNull
    private String description;

    @JsonUnwrapped
    @NonNull
    private EventSource eventSource;

    @NonNull
    private String title;

    @NonNull
    private Category category;

    @NonNull
    @JsonProperty("event_tracks")
    private List<EventTrack> eventsTracks;

    @JsonProperty("protocol_tracks")
    private List<ProtocolTrack> protocolTracks;

    public void addEventTrack(@NonNull EventTrack eventTracks) {
        eventsTracks.add(eventTracks);
    }

    @JsonIgnore
    public EventTrack getLastEventTrack() {
        return eventsTracks.get(eventsTracks.size() - 1);
    }

    @JsonIgnore
    public int getLastPriority() {
        return getLastEventTrack().getPriority();
    }

    public void generateUUIDCode() {
        this.code = UUID.randomUUID().toString();
    }

    @Override
    public int compareTo(Event o) {
        return this.getLastPriority() - o.getLastPriority();
    }
}

So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.

Thank you.


Solution

  • If you want to define multiple representations of the same bean you could use Jackson JsonView.

    With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.

    Documentation here : https://www.baeldung.com/jackson-json-view-annotation

    Just don't forget that you doing REST here....avoid expose too many representations of the same resource