代码之家  ›  专栏  ›  技术社区  ›  liltitus27

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

  •  4
  • liltitus27  · 技术社区  · 7 年前

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

    下面是两个示例配置文件:

    {
        "data": {
            "type": "standard",
            "config": {
                "rows": 1000,
                "columns": 10
            }
        }
    }
    

    {
        "data": {
            "type": "totalSize",
            "config": {
                "sizeInBytes": 1073741824,
                "cellDensityInBytes": 12,
                "columns": 5
            }
        }
    }
    

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

    因此,我创建了一个接口:

    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();
    }
    

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

    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;
    }
    

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

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

    new ObjectMapper().readValue(inputStream, DataGeneratorConfig.class);
    

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

    1 回复  |  直到 7 年前
        1
  •  3
  •   Darren Forsythe    7 年前

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

    但你的思路是正确的,我已经把我认为是你正在寻找的回购的一个可行的例子,注意这是使用 https://projectlombok.org/ 生成POJO,因为我很懒。

    https://github.com/Flaw101/jackson-type-info

    • 它将忽略“数据”节点。这主要是因为我很懒惰,实体可以用 Data 类来处理它。测试中的ObjectMapper启用了此操作所需的功能。
    • 它将读取/写入配置类的数据。与指定的示例内联。
    • 自动取消数据提前化并没有捷径可言。您可以将其写入地图->对象,但这非常混乱,使用lombok/IDE类geneartion等工具,创建这些实体应该需要几秒钟的工作。

    这个 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();
    }
    

    Config只是具体impl的一个标记接口。

    public interface Config {
    
    
    
    }
    

    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;
        }
    }
    

    和类似的 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;
        }
    }
    

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

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

    注意,这使用 assertThat 从…起 AssertJ

    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);
    
        }
    
    }