如何使用 @JsonTypeInfo 和 @JsonSubTypes 实例化具有不同配置的类?

lil*_*s27 5 java json jackson

我想创建一个配置文件,该文件允许我定义不同的数据生成器,每个数据生成器都需要不同的配置。但是,它们都共享相同的方法,generateRow因此这些类都可以实现一个接口。我正在使用 Jackson 版本 2.9.4。

为了说明这一点,这里有两个示例配置文件:

{
    "data": {
        "type": "standard",
        "config": {
            "rows": 1000,
            "columns": 10
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

{
    "data": {
        "type": "totalSize",
        "config": {
            "sizeInBytes": 1073741824,
            "cellDensityInBytes": 12,
            "columns": 5
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个数据生成器只是创建一个具有给定行数和列数的文件,第二个生成器创建一个预定义大小的文件,确定满足配置变量(即列数和单元密度)所需的行数)。

所以,我创建了一个界面:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = IGenerateRows.PROPERTY, defaultImpl = StandardRowGenerator.class)
@JsonSubTypes(value = { @Type(StandardRowGenerator.class) })
public interface IGenerateRows {

    public static final String PROPERTY = "type";

    public String[] generateRow();
}
Run Code Online (Sandbox Code Playgroud)

我至少有一个具体的实现:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;

@JsonTypeName(value = StandardRowGenerator.TYPE)
public class StandardRowGenerator {

    public static final String TYPE = "standard";

    private static final String ROWS = "rows";
    private static final String COLUMNS = "columns";

    @JsonProperty(value = ROWS, required = true)
    private int rows;

    @JsonProperty(value = COLUMNS, required = true)
    private int columns;
}
Run Code Online (Sandbox Code Playgroud)

我不明白的是如何处理config我的配置文件中的数据生成器节点的节点。我如何正确设置我的具体类来定义生成数据所需的属性?

在我的引导代码中,我实例化了整个配置对象,如下所示:

new ObjectMapper().readValue(inputStream, DataGeneratorConfig.class);
Run Code Online (Sandbox Code Playgroud)

为了简洁起见,我省略了 getter 和 setter,以及与当前问题无关的配置文件的其余部分。如果我可以提供任何其他详细信息或代码,请告诉我。

Dar*_*the 4

我有点不确定你的类的底层实现以及它们生成的数据等。

但你的思路是正确的,我已经将你所寻找的工作示例推送到了这个存储库,请注意,这是使用https://projectlombok.org/生成 POJO,因为我很懒。

https://github.com/Flaw101/jackson-type-in​​fo

  • 它将忽略“数据”节点。这主要是因为我又很懒,可以将实体包装在一个Data类中来处理它。测试中的 ObjectMapper 启用了所需的功能。
  • 它将读取/写入配置类的数据。与您指定的示例内联。
  • 自动对数据进行去串化并不能立竿见影。您也许可以将其写入映射 -> 对象,但这非常混乱,并且使用 lombok/IDE 类生成等工具制作这些实体应该只需几秒钟的时间。

看起来IGenerateRow像,

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = RowGenerator.PROPERTY, defaultImpl = StandardRowGenerator.class)
@JsonSubTypes(value = { @Type(StandardRowGenerator.class), @Type(TotalSizeGeneartor.class) })
@JsonRootName(value = "data")
public abstract interface RowGenerator {

    public static final String PROPERTY = "type";

    Config getConfig();
}
Run Code Online (Sandbox Code Playgroud)

Config 只是具体实现的标记接口。

public interface Config {



}
Run Code Online (Sandbox Code Playgroud)

SimpleTypeGenerator 现在变成了,

@JsonTypeName(value = StandardRowGenerator.TYPE)
@Data

public class StandardRowGenerator implements RowGenerator {

    public static final String TYPE = "standard";

    private StandardConfig config;

    @Data
    public static class StandardConfig implements Config {
        private int rows;
        private int columns;
    }
}
Run Code Online (Sandbox Code Playgroud)

类似地TotalSize

@JsonTypeName(value = TotalSizeGeneartor.TYPE)
@Data
public class TotalSizeGeneartor implements RowGenerator {

    public static final String TYPE = "totalSize";

    private TotalSizeConfig config;

    @Data
    public static class TotalSizeConfig implements Config {
        private long sizeInBytes;
        private int cellDensityInBytes;
        private int columns;
    }
}
Run Code Online (Sandbox Code Playgroud)

这些可以通过更多/更好的通用类型信息来改进,以便能够获得对配置的具体引用。

测试类读取资源文件夹中的两个配置,将它们写入对象并返回到字符串,比较之前/之后,不存在 null 或空属性,并且接口具有正确的实现。

注意,这使用了assertThatfromAssertJ

public class JacksonTest {

    private ObjectMapper mapper;
    private String json;

    @Before
    public void setup() throws Exception {
        mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
        mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
    }

    @Test
    public void testDeserStandard() throws Exception {
        json = StringUtils.deleteWhitespace(
                new String(Files.readAllBytes(Paths.get("src/main/resources/standard.json")), StandardCharsets.UTF_8));

        RowGenerator generator = mapper.readValue(json, RowGenerator.class);
        assertThat(generator).hasNoNullFieldsOrProperties().isExactlyInstanceOf(StandardRowGenerator.class);
        assertThat(generator.getConfig()).hasNoNullFieldsOrProperties().isExactlyInstanceOf(StandardConfig.class);
        assertThat(json).isEqualTo(mapper.writeValueAsString(generator));
        System.out.println(generator);
    }

    @Test
    public void testDeserTotalsize() throws Exception {
        json = StringUtils.deleteWhitespace(
                new String(Files.readAllBytes(Paths.get("src/main/resources/totalsize.json")), StandardCharsets.UTF_8));

        RowGenerator generator = mapper.readValue(json, RowGenerator.class);
        assertThat(generator).hasNoNullFieldsOrProperties().isExactlyInstanceOf(TotalSizeGeneartor.class);
        assertThat(generator.getConfig()).hasNoNullFieldsOrProperties().isExactlyInstanceOf(TotalSizeConfig.class);
        assertThat(json).isEqualTo(mapper.writeValueAsString(generator));
        System.out.println(generator);

    }

}
Run Code Online (Sandbox Code Playgroud)