Чтение поля с типом интерфейс с помощью Jackson

Имеется класс с полем содержащим интерфейс.

Мой yaml выглядит так:

field_name: # Название поля в классе
  type: TYPE1
  data:
    amount: 1

В моем лоадере имеется мапа Map<String, Class<? extends MyInterface> которая определяет класс по значение type из yaml.

Как я могу реализовать загрузку данного объекта?

Обновление

Вот пример класса с полем:

public class MyClass {
    private MyInterface field_name;
}

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

public class Loader {
    private static final Map<String, Class<? extends MyInterface> TYPES = new HashMap<>();

    public static void Initialize() {
        File itemsDir = Resources.Load("items");

        if (!itemsDir.exists() || !itemsDir.isDirectory()) {
            throw new RuntimeException("Directory container not found!");
        }

        File[] itemFiles = itemsDir.listFiles();
        for (File itemFile : itemFiles) {
            YAMLMapper yaml = new YAMLMapper();
            try {
                MyClass item = yaml.readValue(itemFile, MyClass.class); // Тут будет ошибка
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Ответы (1 шт):

Автор решения: Nowhere Man

При использовании Jackson библиотеки стандартный подход состоит в применении аннотаций @JsonTypeInfo / @JsonSubtypes для описания интерфейса, его привязки к конкретным классам-реализациям при помощи дополнительного поля (например, type) или yaml-тэгов.

Пример.
Требуется десериализовать объект, содержащий список интерфейсов:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import my.code.IFoo;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Config {
    private List<IFoo> data;
}
  • интерфейс my.code.IFoo

Информацию о типах можно указать, как с использованием поля type, так и при помощи YAML-тэгов:

package my.code;

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

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Fee.class, name = "fee"),
    @JsonSubTypes.Type(value = Fee.class, name = "tag:yaml.org,2002:fee"), // !!fee в yaml-файле
    @JsonSubTypes.Type(value = Foo.class, names = {"foo", "tag:yaml.org,2002:foo"})
})
public interface IFoo {}

Соответственно, реализации интерфейса могут быть совершенно различными:

package my.code;

import lombok.*; 

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Foo implements IFoo {
    private String foo;
    private int bar;
}
package my.code;

import lombok.*; 

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Fee implements IFoo {
    private String fee;
    private int buz;
}

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

# data.yaml
data:
  -
    !!foo
    foo: Hello Yaml Tag!
    bar: 2024
  -
    type: foo
    foo: Hello Type Field!
    bar: 2200
  -
    !!fee
    fee: Bye-bye YAML tag!
    buz: 2012
  -
    type: fee
    fee: Bye type field!
    buz: 2000

Тогда при десериализации объекта со списком интерфейсов получим результат:

ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());

Config config = objectMapper.readValue(new File("data.yaml"), Config.class);

config.getData().forEach(System.out::println);
Foo(foo=Hello Yaml Tag!, bar=2024)
Foo(foo=Hello Type Field!, bar=2200)
Fee(fee=Bye-bye YAML tag!, buz=2012)
Fee(fee=Bye type field!, buz=2000)

Зависимость в pom.xml для обработки YAML-формата в Jackson:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>${jackson.version}</version>
</dependency>
→ Ссылка