摘要
Jackson中利用@JsonTypeInfo可以实现多态的序列化和反序列化,从而达到灵活的编码目的。
最佳实践
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true,
defaultImpl = Action.class, include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = CallPhoneAction.class, name = "CALL_PHONE"),
@JsonSubTypes.Type(value = SendEmailAction.class, name = "SEND_EMAIL"),
})
public class Action {
/**
* 动作类型
*/
private String type;
}
@Data
@EqualsAndHashCode(callSuper = true)
public class CallPhoneAction extends Action {
/**
* 电话号码
*/
private String phoneNumber;
/**
* 语音内容
*/
private String content;
}
@Data
@EqualsAndHashCode(callSuper = true)
public class SendEmailAction extends Action {
/**
* 对方的邮箱
*/
private String toEmailAddress;
/**
* 抄送的邮箱
*/
private String ccEmailAddress;
/**
* 邮件标题
*/
private String title;
/**
* 邮件内容
*/
private String content;
}
测试代码
@Slf4j
public class RunningApplicationTest {
static private ObjectMapper objectMapper = new ObjectMapper();
@Test
public void test() throws JsonProcessingException {
String SendEmailJson = "" +
"{\n" +
" \"type\": \"SEND_EMAIL\",\n" +
" \"toEmailAddress\": \"receive@example.com\",\n" +
" \"title\": \"这是邮件标题\",\n" +
" \"content\": \"这是邮件内容\"\n" +
"}";
Action action = objectMapper.readValue(SendEmailJson, Action.class);
log.info("the claas name of action is {}", action.getClass().getSimpleName());
log.info("{}", action);
}
}
输出
11:52:23.199 [main] INFO org.example.RunningApplicationTest - the claas name of action is SendEmailAction
11:52:23.203 [main] INFO org.example.RunningApplicationTest - SendEmailAction(toEmailAddress=receive@example.com, ccEmailAddress=null, title=这是邮件标题, content=这是邮件内容)
用于生产中的demo
@RestController
@RequestMapping("action/")
public class Controller {
@PostMapping
public Action submit(@RequestBody Action action) {
if (action instanceof CallPhoneAction) {
// 执行打电话动作
} else if (action instanceof SendEmailAction) {
// 执行发邮件动作
} else {
//输入的type错误,导致 action 没有被反序列化成子类
throw new RuntimeException("Type unrecognized. " + action);
}
return action;
}
}
@JsonTypeInfo 解释
@JsonTypInfo
用于配置JSON 序列化和反序列化过程中使用哪种类型信息。该注解是实现多态类型的必要注解。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true,
defaultImpl = Action.class, include = JsonTypeInfo.As.EXISTING_PROPERTY)
- use:用于指定用哪种类型的元信息来序列化和反序列化。有下面几种选择
- JsonTypeInfo.Id.NONE: JSON字符串中不包含显式的元信息用于识别类型。开发人员使用其他的标识手段来确定类型。一般用不到。
- JsonTypeInfo.Id.CLASS: 使用全限定名来识别类型。好处是不用写@JsonSubTypes,缺点是JSON强依赖代码,一旦代码改动,JSON也要跟着变化。
输出为@Data @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "type", visible = true, defaultImpl = Action.class, include = JsonTypeInfo.As.EXISTING_PROPERTY) public class Action { /** * 动作类型 */ private String type; } @Slf4j public class RunningApplicationTest { @Test public void test2() throws JsonProcessingException { String json = "{\n" + " \"type\": \"org.example.dao.SendEmailAction\",\n" + " \"toEmailAddress\": \"receive@example.com\",\n" + " \"ccEmailAddress\": null,\n" + " \"title\": \"这是邮件标题\",\n" + " \"content\": \"这是邮件内容\"\n" + "}"; Action action = objectMapper.readValue(json, Action.class); log.info("the claas name of action is {}", action.getClass().getSimpleName()); log.info("{}", action); } }12:13:33.237 [main] INFO org.example.RunningApplicationTest - the claas name of action is SendEmailAction 12:13:33.241 [main] INFO org.example.RunningApplicationTest - SendEmailAction(toEmailAddress=receive@example.com, ccEmailAddress=null, title=这是邮件标题, content=这是邮件内容)- JsonTypeInfo.Id.MINIMAL_CLASS:与JsonTypeInfo.Id.CLASS类似。区别展示可唯一定位一个类的最小路径。例如这里SendEmailAction的最小路径是
"type":".SendEmailAction"。其中"."是不可获取的,表示相对路径的起点。 - JsonTypeInfo.Id.NAME:逻辑名称,配合@JsonSubTypes可以实现自定义标识。在本例中笔者自定义标识符为
"CALL_PHONE"和"SEND_EMAIL" - JsonTypeInfo.Id.CUSTOM: 表示类型化机制使用自定义处理,需要自定义指向Jackson中的类型化机制。一般不用。
- property:指定哪个字段用于标识类型。本例中,笔者使用
"type"字段作为类型标识字段。 - visible:当为
true时,property中用到的字段将参与序列化和反序列化中;为false时不参与序列化和反序列化。 - defaultImpl:指定默认类,用于Jackson不能反序列化为任意一个子类时使用。可以填写
Void,将反序列化为null。默认值为JsonTypeInfo.class,抛出com.fasterxml.jackson.databind.exc.InvalidTypeIdException异常。 - include:指定要如何处理类型标识。
- JsonTypeInfo.As.PROPERTY:序列化时,类型标识字段作为json的一个字段。此时无论json中是否已经存在该字段。因此
visible = true和include = JsonTypeInfo.As.EXISTING_PROPERTY搭配时,会导致序列化后json有两个重复的字段,本例中是重复的type=SEND_EMAIL - JsonTypeInfo.As.WRAPPER_OBJECT: 序列化时,在json中类型标识字段会使用大括号包裹起来
- JsonTypeInfo.As.WRAPPER_ARRAY: 序列化时,在json中类型标识字段会使用中括号包裹起来
- JsonTypeInfo.As.EXTERNAL_PROPERTY: 序列化时,在json中类型标识字段和序列化的类同级
- JsonTypeInfo.As.EXISTING_PROPERTY: 序列化时,如果json中已经有同名字段,那么类型标识字段不序列化。
- JsonTypeInfo.As.PROPERTY:序列化时,类型标识字段作为json的一个字段。此时无论json中是否已经存在该字段。因此