Чтение поля с типом интерфейс с помощью 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 шт):
При использовании 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>