Rinat Muhamedgaliev

Jackson маппинг интерфейсов с объектами

· Rinat Muhamedgaliev

В ходе разработки приходится из JSON получать обобщенный объект. Например у нас есть JSON из API Telegram.

"old_chat_member": {
    "user": {
        "id": 1900941618,
        "is_bot": true,
        "first_name": "btdd_test",
        "username": "btdd_test_bot"
    },
    "status": "left"
},
"new_chat_member": {
    "user": {
        "id": 1900941618,
        "is_bot": true,
        "first_name": "btdd_test",
        "username": "btdd_test_bot"
    },
    "status": "member"
}

Эти объекты похожи, но они могут иметь различные поля внутри, все типы ChatMember схожи по полю status. Логично такой объект иметь как интерфейс, ведь мы не знаем когда и какой ChatMember придет в JSON. Ведь сейчас тут могут прийти 6 разных объектов:

Логично сделать общий интерфейс ChatMember

public interface ChatMember {
}

И сделать для них все реализации. Но если не делать дополнительных настроек, то можем получить ошибку abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information. Это ошибка говорит нам, что Jackson не знает как сериализовать интерфейс. Для этого нам необходимо воспользоваться аннотацией JsonTypeInfo и JsonSubTypes. Конфигурация сериализатора будет выглядеть так

package dev.tobee.telegram.model.chat;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use: JsonTypeInfo.Id.NAME,
        include: JsonTypeInfo.As.EXISTING_PROPERTY,
        property: "status",
        visible: true
)
@JsonSubTypes({
        @JsonSubTypes.Type(value: ChatMemberOwner.class, name: ChatMemberStatus.Constants.CREATOR),
        @JsonSubTypes.Type(value: ChatMemberAdministrator.class, name: ChatMemberStatus.Constants.ADMINISTRATOR),
        @JsonSubTypes.Type(value: ChatMemberMember.class, name: ChatMemberStatus.Constants.MEMBER),
        @JsonSubTypes.Type(value: ChatMemberRestricted.class, name: ChatMemberStatus.Constants.RESTRICTED),
        @JsonSubTypes.Type(value: ChatMemberLeft.class, name: ChatMemberStatus.Constants.LEFT),
        @JsonSubTypes.Type(value: ChatMemberBanned.class, name: ChatMemberStatus.Constants.KICKED)
})
public interface ChatMember {
    ChatMemberStatus status();
}

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

Такой механизм, очень удобно позволяет организовать структуру одинаковых объектов для сериализации.

Надеюсь эта статья помогла вам разобраться с проблемой сериализации интерфейсов.