杰克逊:当调用不同的 Rest EndPoint 时,同一实体上的多个序列化器

Posted

技术标签:

【中文标题】杰克逊:当调用不同的 Rest EndPoint 时,同一实体上的多个序列化器【英文标题】:Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called 【发布时间】:2020-08-04 07:30:54 【问题描述】:

我试图避免在调用不同的 EndPoint 时使用 DTO 反模式,其中每个都返回同一实体的不同表示。当我在 Rest EndPoint 中返回实体时,我想利用 Jackson 执行的序列化。这意味着序列化只进行一次,而不是使用 DTO(实体到 DTO 和 DTO 到 Json):

端点示例:

@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(扩展 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
    

实体:

@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();
    

所以,到目前为止,我已经能够使用扩展 StdDeserializer 的类来序列化表示类型,但这并不能让我灵活地以多种方式扩展相同实体属性的表示。虽然我已经尝试过使用 Json 注释,但我意识到实体类具有的表示越多,它可能会变得非常复杂,它应该很简单。也许有些想法我该怎么做。

谢谢。

【问题讨论】:

为什么你认为 DTO 是一种反模式?您可以直接从查询中创建 DTO。这是一个非常好的模式。 并且实体到 DTO 的转换,除非它们在不同的机器上运行不是 serialization.. 它只是 conversion.. 在同一个 jvm 上仍然是 java 对象到另一个 java 对象。 @SimonMartinell 我想是因为我重复信息的程度几乎可以完全重复实体类。为什么不使用基类并使用负责的类对其进行转换以直接对其进行序列化,Json Serializaer? 不要只考虑将实体转换为 DTOS。更喜欢直接从数据库加载好的 dto ......这将避免资源浪费,因为在这里你总是加载完整的实体,然后使用序列化程序过滤一些字段......这是浪费...... @CodeScale 这是一个非常好的观点,我喜欢它。虽然用基础实体也带来或请求必要的信息,这满足抽象设计原则。所以我一直在寻找一个单独负责转换 Json 对应表示的 Jackson 类。现在,我会听从你的建议。 【参考方案1】:

如果你想定义同一个 bean 的多个表示,你可以使用 Jackson JsonView

使用 json 视图,您可以设置不同的策略来定义将在响应中序列化的属性,因此通过端点使用不同的 views

此处的文档:https://www.baeldung.com/jackson-json-view-annotation

只是不要忘记你在这里做 REST....避免暴露同一资源的太多表示

【讨论】:

我看过那个注释,但它也不允许我需要的灵活性和可扩展性。想象一下,您有 10 种或更多不同的表示形式,JsonView 变得非常难以处理。所以我的问题是你是否可以避免使用 StdSerializer 类来使用 DTO 来进行多重表示。 不适用于Serializer。仅使用视图,因为您可以将视图链接到特定端点 问题:而不是使用杰克逊序列化程序...为什么不使用自定义 bean,通过将忽略的字段设置为 null 来充当过滤器。然后将其添加到您的实体JsonInclude(Include.NON_NULL)。那么你可以通过端点调用这个自定义过滤器...... 我个人更喜欢使用JsonView,因为你会在这里重新发明***......但这是一个选择。 别忘了你在这里做 REST....避免同一资源的过多表示

以上是关于杰克逊:当调用不同的 Rest EndPoint 时,同一实体上的多个序列化器的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring WebFlux Client 使用 REST Endpoint 时出错

对象序列化到字节与杰克逊序列化从对象到 JSON?

当rest控制器返回null body时,ajax转到error方法

热到让杰克逊在 Spring Boot REST API 中按需使用蛇案例/骆驼案例?

改造:用杰克逊反序列化失败,没有任何错误

[JavaEE] Implement a REST Endpoint