Polymorphi c @RequestBody в Spring-Boot - PullRequest
       32

Polymorphi c @RequestBody в Spring-Boot

1 голос
/ 29 января 2020

Проблема довольно проста. У меня есть пара событий, происходящих из одного и того же интерфейса, и я бы хотел десериализовать их в свой суперкласс propper.

Я знаю, как это сделать с помощью объектного сопоставителя, но использую свой собственный преобразователь. означало бы позволить Spring-Boot анализировать @RequestBody как String, а затем делать это самостоятельно, что не является концом света, но я не могу не предположить, что Spring предоставляет надлежащие инструменты для работы с такой ситуацией. Проблема в том, что я не могу их найти.

Вот пример кода:

пример события:

interface YellowOpsEvent {
    val user: String
    val partner: String
    val subject: String
    val change: NatureOfChange
}

data class StatusChangedEvent(override val user: String,
                              override val partner: String,
                              override val subject: String,
                              val before: String,
                              val after: String): YellowOpsEvent {
    override val change = NatureOfChange.Changed
}

контроллер:

@PostMapping("/event")
    fun writeEvent(@RequestBody event: YellowOpsEvent) {  // < I expect this not to throw an exception
        val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
    }

Просто чтобы уточнить, я прекрасно понимаю , почему это не работает из коробки. Проблема в том, что я не могу понять, что мне нужно сделать, чтобы это сработало.

1 Ответ

1 голос
/ 30 января 2020

Ссылка в комментарии pL4Gu33 привела меня в правильном направлении, но потребовались некоторые дополнительные поиски и манипуляции, собирая информацию здесь и там, чтобы найти решение, которое наконец заработало бы, поэтому я подытоживаю его здесь для полноты.

Беда в том, что вам понадобятся две аннотации, одна для интерфейса и одна для реализующих классов, комбинированное использование которых выглядит несколько плохо документированным. Сначала на интерфейсе добавьте эту аннотацию. В отличие от некоторых учебных пособий, дальнейшая аннотация интерфейса не требуется:

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
interface YellowOpsEvent {
    val user: String
    val partner: String
    val subject: String
    val change: NatureOfChange
}

Согласно некоторым документам, одного этого должно быть достаточно для десериализации пропплера. Однако контроллер весенней загрузки выдает исключение, поскольку переданное имя root не соответствует ожидаемому классу.

// the above will throw an exception when the serialization product is sent to this controller:

    @PostMapping("/event")
        fun writeEvent(@RequestBody event: YellowOpsEvent) {  // < I expect this not to throw an exception
            val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
        }

Чтобы исправить это, добавьте аннотацию @JsonRootName для любых реализующих классов , с именем интерфейса . Большая часть документации этой аннотации не использует ее для этого, а просто для переименования типа, и даже когда она упоминается в связанном вопросе в контексте полиморфизма, она ошибочно использует свое собственное имя. Вот как это должно выглядеть:

@JsonRootName("YellowOpsEvent")
data class StatusChangedEvent(override val user: String,
                              override val partner: String,
                              override val subject: String,
                              val before: String,
                              val after: String): YellowOpsEvent {
    override val change = NatureOfChange.Changed
}

Теперь это работает! :)

...