Java利用Jackson转换多态结构的数据

1. 问题

在工作中我们常遇到一个场景,以下Json结构,需要根据Json字符串的 type 字段来选择转换对应的实体类,一般情况就是可能先将字符串转换为 Map 结构然后获取到 type 字段然后判断具体类型,再转换成实体,这样很麻烦,这里我们可以利用 Jackson 框架提供的能力来进行多态的实体转换。

{
    "payload": [
        {
            "type": "Payload100",
            "payload": {
                "name": "zs"
            }
        },
{
            "type": "Payload200",
            "payload": {
                "name": "zs"
            }
        }
    ]
}

上面是每个实体转换时都按照各自的类型进行实例化,下面这种就是按照实体中的某个字段进行转换

{
	"type": "Payload100",
	"payload": {
		"name": "ls"
     }
}

2. 解决方案一

按照上面的结构我们可以先定义一个接口,其中上面标记的注解后续进行详解

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type",
    visible = true //定义了标识符是否进行序列化,默认false,反序列化时不会包含type字段
)
@JsonSubTypes(
    value = {
        @JsonSubTypes.Type(value = Payload100.class, name = "Payload100"),
        @JsonSubTypes.Type(value = Payload200.class, name = "Payload200")
    }
)
public interface JacksonInterface {

}

定义两个实体类来实现上面的接口,一个 Payload100 一个 Payload200

@Data
public class Payload100 implements JacksonInterface {
    private String name;
}
@Data
public class Payload200 implements JacksonInterface {
    private String name;
}

定义一个包含的实体类,如果外层不包括实体,会导致 type 生成标识符的丢失

@Data
public class Payload {
	private List<JacksonInterface> payload;
}

测试结果,从下面测试代码可以看到,我们先将 Payload 序列化成字符串,然后在每个 payload 下会生成一个 type 字段,这样的json字符串就可以进行相互转换

@Test
    public void test_Jackson_serializer() throws IOException {
        Payload payload = new Payload();
        Payload100 payload100 = new Payload100();
        payload100.setName("zs");
        Payload200 payload200 = new Payload200();
        payload200.setName("ls");
        List<JacksonInterface> list = new ArrayList<>();
        list.add(payload100);
        list.add(payload200);
        payload.setPayload(list);
        String s = objectMapper.writeValueAsString(payload);
        System.out.println("序列化成字符串:" + s);
        System.out.println("转换后的实体类结果:");
        System.out.println(objectMapper.readValue(s, Payload.class));
    }

在这里插入图片描述

2.1 注解

@JsonTypeInfo

用于标记json转换时需要生成的字段名称以及从那里取到对应的字段

  • use:采用什么样的数据生成标识符
    • NAME:通过名称进行生成,需要配合 @JsonSubTypes 注解一起食用
    • NONE:不配置按照默认方式
    • CLASS:获取到类名,通过类名进行生成(这个方式会暴露类名出去,不安全)
    • MINIMAL_CLASS:按照主类的类名进行生成
    • CUSTOM:自定义生成方式,需要配合 @JsonTypeIdResolver 注解一起食用自定义标识符的生成器
  • include:通过什么样的方式生成标识符
    • PROPERTY:生成一个属性字段在json字符串中
    • WRAPPER_OBJECT:按照一个对象生成到字符串中
    • WRAPPER_ARRAY:封装成数组生成
    • EXTERNAL_PROPERTY:通过外部生成的字段
    • EXISTING_PROPERTY:通过已经存在的字段生成
  • property:生成标识的名称

@JsonSubTypes

自定名称对应的子类型有哪些,里面包含了一个 @JsonSubTypes.Type 用于指定名称和类的对应

@JsonTypeIdResolver

自定义标识符生成器,自定义标识符的生成器的好处是可以不需要在 @JsonSubTypes 中每新增一个类就要添加一个子类名称对应,需要实现 TypeIdResolver 接口其中方法如下:

  • init:初始化
  • idFromValue:从对象中获取(序列化时)
  • idFromValueAndType:从对象和类中来获取(序列化时)
  • idFromBaseType:从基础类型中进行获取
  • typeFromId:反序列化通过标识符进行转换

下面有个例子,具体使用可以自行探索:

public class IdResolver implements TypeIdResolver {

    private JavaType javaType;

    @Override
    public void init(JavaType javaType) {
    }

    @Override
    public String idFromValue(Object o) {
        return idFromValueAndType(o, o.getClass());
    }

    @Override
    public String idFromValueAndType(Object o, Class<?> aClass) {
        String value = null;
        try {
            Field field = aClass.getDeclaredField(Payload.PAYLOAD_FILED_NAME);
            field.setAccessible(true);
            Object payload = field.get(o);
            if (payload != null && JacksonInterface.class.isAssignableFrom(payload.getClass())) {
                Type type = payload.getClass().getAnnotation(Type.class);
                if (type != null) {
                    value = type.value();
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return value;
    }

    @Override
    public String idFromBaseType() {
        return idFromValueAndType(null, this.javaType.getClass());
    }

    @Override
    public JavaType typeFromId(DatabindContext databindContext, String s) throws IOException {
        ServiceLoader<JacksonInterface> jacksonInterfaces = ServiceLoader.load(JacksonInterface.class);
        return this.javaType;
    }

    @Override
    public String getDescForKnownTypeIds() {
        return null;
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;
    }
}

3. 解决方案二

如果要按照方案二进行json的转换,那么就需要自定义序列化和反序列化器了,下面是实战案例

3.1 PayloadSerialize

定义序列化器,将实体类属性转换成 Json字符串

public class PayloadSerialize extends StdSerializer<Payload> {

    private static final long serialVersionUID = 7679701332948432903L;

    protected PayloadSerialize() {
        this(null);
    }

    protected PayloadSerialize(Class<Payload> t) {
        super(t);
    }

    @Override
    public void serialize(Payload value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        JacksonInterface payload = value.getPayload();
        gen.writeStartObject();
        if (payload != null) {
            Type type = payload.getClass().getAnnotation(Type.class);
            if (type != null) {
                gen.writeStringField("type", type.value());
            }
        }
        gen.writeObjectField("payload", payload);
        gen.writeEndObject();
    }
}

3.2 PayloadDeserialize

定义反序列化器,主要目的是将 Json字符串 中对应的标识符,转换成对应的实体类型,下面对 Payload 类型判断是写死了,这里可以改用 SPI 来读取对应的接口实现类,SPI 的时候后续我在单独开一个专栏写

public class PayloadDeserialize extends StdDeserializer<Payload> {

    private static final long serialVersionUID = 8851541065777997216L;

    protected PayloadDeserialize() {
        this(null);
    }


    protected PayloadDeserialize(Class<Payload> t) {
        super(t);
    }

    @Override
    public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        TreeNode treeNode = p.getCodec().readTree(p);
        String type = null;
        TreeNode typeNode = treeNode.get("type");
        if (TextNode.class.isAssignableFrom(typeNode.getClass())) {
            type = ((TextNode) Objects.requireNonNull(typeNode, "type标识符不能为空")).textValue();
        }
        Payload payload = new Payload();
        Field[] fields = Payload.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                Class<?> fieldType = field.getType();
                TreeNode node = treeNode.get("payload");
                if (JacksonInterface.class.isAssignableFrom(fieldType)) {
                    if ("Payload100".equalsIgnoreCase(type)) {
                        Payload100 payload100 = node.traverse(p.getCodec()).readValueAs(Payload100.class);
                        field.set(payload, payload100);
                    }
                }

            } catch (IllegalAccessException ignored) {

            }
        }
        return payload;
    }
}

3.3 Payload

这里标记 Payload 需要的序列化器和反序列化器,其中 type 字段不需要定义,通过 payload 字段中的 @Type 注解来标记是什么类型

@Data
@JsonSerialize(using = PayloadSerialize.class)
@JsonDeserialize(using = PayloadDeserialize.class)
public class Payload {
    private JacksonInterface payload;
}

3.4 @Type

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Type {
    String value();
}

转换后的结果显示,可以实现第二种 Json 结构的转换

@Test
    public void test_Jackson_serializer() throws IOException {
        Payload payload = new Payload();
        Payload100 payload100 = new Payload100();
        payload100.setName("zs");
        payload.setPayload(payload100);
        String s = objectMapper.writeValueAsString(payload);
        System.out.println("序列化成字符串:" + s);
        System.out.println("转换后的实体类结果:");
        System.out.println(objectMapper.readValue(s, Payload.class));
    }

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值