如何根据父类中可用的信息进行反序列化

时间:2017-02-03 11:17:47

标签: java json generics enums jackson

我正在使用Jackson来反序列化Product接口的许多不同实现。这些产品实现具有不同的字段,但都具有InsuredAmount字段。此InsuredAmount类具有值字段和IAType字段。 IAType是一个标记接口,具有不同的枚举作为实现。

现在问题在于:IAType接口的枚举实现对应于Product接口的某个实现。如何制作通用实现并告诉Jackson找到正确的IAType实现?我应该在Product和IAType接口上使用通用参数来识别产品实现吗?我应该在识别产品实现的类上使用Productable功能接口吗?我如何告诉杰克逊使用该实现?

我希望下面的代码能够澄清问题,我选择在这里实现一个Productable接口,但是欢迎使用更好的结构来处理这个问题。

@JsonPropertyOrder({"type", "someInfo"})
public class InsuredAmount implements Productable, Serializable {

    private static final long serialVersionUID = 1L;

    private IAType type;

    private String someInfo;

    public InsuredAmount() {
    }

    public InsuredAmount(IAType typeA, String someInfo) {
        this.type = typeA;
        this.someInfo = someInfo;
    }


    /* This should be on the product level, but if I can solve this problem,
    the next level will just be more of the same.
    */
    @JsonIgnore
    @Override
    public Product getProduct() {
        return Product.PROD_A;
    }

    // Getters, setters, equals, etc. omitted.
}

-

public interface Productable {

    public Product getProduct();

}

-

public enum Product {

    PROD_A, PROD_B;

}

-

@JsonDeserialize(using = IATypeDeserializer.class)
public interface IAType extends Productable {

}

-

public enum IATypeA implements IAType {

    FOO, BAR;

    @Override
    public Product getProduct() {
        return Product.PROD_A;
    }

}

-

public class IATypeDeserializer extends StdDeserializer<IAType> {

    private static final long serialVersionUID = 1L;

    public IATypeDeserializer() {
        this(null);
    }

    public IATypeDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public IAType deserialize(JsonParser parser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        JsonNode node = parser.getCodec().readTree(parser);
        /* How to find out that the class calling the deserialization is InsuredAmountA, which
        has getProduct() method that returns PROD_A, and matches the IATypeA that also returns
        PROD_A, so I know to deserialize IATypeA, instead of other implementations of the IAType
        interface?
        */
        return IATypeA.valueOf(node.asText());
    }

}

-

public class InsuredAmountTest {

    private final ObjectMapper mapper = new ObjectMapper();

    @Test
    public void test01() throws IOException {
        InsuredAmount iaA = new InsuredAmount(IATypeA.FOO, "test it");
        String json = mapper.writeValueAsString(iaA);
        assertThat(json, is("{\"type\":\"FOO\",\"someInfo\":\"test it\"}"));
        InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
        IAType type = iaA2.getType();
        assertThat(type, is(IATypeA.FOO));
        assertThat(type.getProduct(), is(Product.PROD_A));
        assertThat(iaA, is(iaA2));
    }

    @Test
    public void test02() throws IOException {
        InsuredAmount iaA = new InsuredAmount(IATypeA.BAR, "test it");
        String json = mapper.writeValueAsString(iaA);
        assertThat(json, is("{\"type\":\"BAR\",\"someInfo\":\"test it\"}"));
        InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
        assertThat(iaA, is(iaA2));
    }

}

3 个答案:

答案 0 :(得分:3)

杰克逊处理枚举序列化的过程非常简单,因此您需要做的只是使用IAType注释@JsonTypeInfo字段:

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
private IAType type;

然后进行测试:

public static void main(String[] args) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
    System.out.println(json);
    InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
    System.out.println("Type is: " + ia.getType());
}

导致输出:

{"type":[".IATypeA","FOO"],"someInfo":"info"}
Type is: FOO

要获得更紧凑的表示,您必须使用自定义序列化。假设您的枚举命名空间中没有重叠,您可以将类型字段序列化为枚举名称。

反序列化器需要知道哪些类型可用于构造,可以通过类路径发现,或者如下例所示,简单地对引用进行硬编码:

public class IATest {

    public static class IATypeSerializer extends JsonSerializer<IAType> {
        @Override
        public void serialize(IAType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(((Enum) value).name());
        }
    }

    public static class IATypeDeserializer extends JsonDeserializer<IAType> {
        @Override
        public IAType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            String value = p.readValueAs(String.class);
            try {
                return IATypeA.valueOf(value);
            } catch (IllegalArgumentException e) {
                // fall through
            }
            try {
                return IATypeB.valueOf(value);
            } catch (IllegalArgumentException e) {
                // fall through
            }
            throw new JsonMappingException(p, "Unknown type '" + value + "'");
        }
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        // Register a module to handle serialization of IAType implementations
        SimpleModule module = new SimpleModule();
        module.addSerializer(IAType.class, new IATypeSerializer());
        module.addDeserializer(IAType.class, new IATypeDeserializer());
        mapper.registerModule(module);

        // Test
        String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
        System.out.println(json);
        InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
        System.out.println("Type is: " + ia.getType());
    }

}

哪个输出:

{"type":"FOO","someInfo":"info"}
Type is: FOO 

答案 1 :(得分:2)

我最终在特殊构造函数上使用JsonCreator注释。

    @JsonCreator
    public InsuredAmountA(
            @JsonProperty("type") String type,
            @JsonProperty("someInfo") String someInfo) throws IOException {
        switch (getProduct()) {
            case PROD_A:
                try {
                    this.type = IATypeA.valueOf(type);
                    break;
                } catch (IllegalArgumentException ex) {
                    // Throw IOException in the default.
                }
//            case PROD_B:
//                this.type = (IATypeB) typeA;
//                break;
            default:
                throw new IOException(String.format("Cannot parse value %s as type.", type));
        }
        this.someInfo = someInfo;
    }

答案 2 :(得分:0)

您可以研究多态反序列化的方向:

http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

unsing自定义类型解析器