【发布时间】:2017-09-10 14:15:14
【问题描述】:
我正在编写一个使用 Jackson 进行序列化的 Spring Boot 应用程序(RESTful Web 服务)。我有以下数据模型,它们将在服务及其 HTTP 客户端之间来回发送(因此这些数据模型将被序列化/反序列化为 JSON):
public abstract class BaseEntity {
@JsonIgnore
private Long id;
private UUID refId;
// Getters, setters, ctors, etc.
}
public abstract class BaseLookup extends BaseEntity {
private String name;
private String label;
private String description;
// Getters, setters, ctors, etc.
}
public class State extends BaseLookup {
private String abbrev; // "VT", "FL", etc.
// Getters, setters, ctors, etc.
}
public class Contact extends BaseEntity {
private String givenName;
private String surname;
private State state;
// Getters, setters, ctors, etc.
}
public class Account extends BaseEntity {
private Contact contact;
private String code;
// lots of other fields that will be generated server-side
// Getters, setters, ctors, etc.
}
因此会有一些端点用于 CRUDding Accounts,其他端点用于 CRUDding Contacts 等。例如,AccountController 将暴露端点用于 CRUDding Account 实例:
@RestController
@RequestMapping(value = "/accounts")
public class AccountController {
@RequestMapping(method = RequestMethod.POST)
public void createAccount(@RequestBody Account account) {
// Do stuff and persist the account to the DB
}
}
我想简化 HTTP 客户端必须制作的 JSON,以便创建新的 Account、Contact 等实例。同时,我不想向客户端公开这些数据模型上的字段。 BaseEntity#id 之类的东西(这是数据库中实体的 PK)。或者例如,在State 的情况下,我只想让客户端知道(并使用)abbrev 字段等。我不希望他们看到其他 BaseLookup 字段或甚至知道他们。
因此,我的最终目标是允许客户端发布以下 JSON,并让 custom Jackson deserializer 将该 JSON 转换为 Account 实例:
{
"contact" : {
"givenName" : "Him",
"surname" : "Himself",
"state" : "NY"
},
"code" : "12345"
}
所以你看,就像我上面所说的,这个 JSON 完成了几件事:
- 在 POST 以创建新实例时,客户端不提供
BaseEntity#id或BaseEntity#refId - 对于
contact.state字段,这是一个带有多个字段(id、refId、name、label、description、abbrev)的BaseLookup,用户只需提供abbrev字段,反序列化程序将确定客户端指的是哪个State -
Account类实际上有许多其他字段是在服务器端推断/生成的;客户端无需了解它们即可创建Account实例 - 上面的 JSON 是我们使用 Jackson 的默认行为序列化
Account得到的简化形式;这是为了让客户端的事情更容易,在服务器端更安全(不暴露 PK 等)
这里需要注意的重要一点是发送到此控制器的 contact 字段的 JSON 与将发送到 ContactController 以创建新的 Contact 的 JSON 相同实例。
问题来了:
public class AccountDeserializer extends StdDeserializer<Account> {
public AccountDeserializer() {
this(null);
}
public AccountDeserializer(Class<Account> accClazz) {
super(accClazz);
}
@Override
public Account deserialize(JsonParser jsonParser, DeserializationContext dCtx)
throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.codec.readTree(jsonParser)
Contact contact = ??? // TODO: How to invoke ContactDeserializer here?
String accountCode = node.get("code").asText();
// Generate lots of other Account field values here...
Account account = new Account(contact, accountCode, /* other fields here */);
return account;
}
}
因为我还将有一个 ContactController(用于 CRUDding Contact 实例,而不管关联的 Account),并且因为我有类似的愿望从客户端隐藏 Contact 字段以及简化JSON 进入这个ContactController#createContact 端点,除了这个AccountDeserializer 之外,我还需要一个ContactDeserializer...
public class ContactDeserializer extends StdDeserializer<Contact> {
// ...etc.
}
这个ContactDeserializer 将负责将JSON 转换为Contact 实例。但是由于Account 实例还包含Contact 实例,并且由于外部“帐户 JSON”中的“联系人 JSON”将与客户端发送到任何“联系人端点”的任何 JSON 相同,所以我会喜欢以某种方式从AccountDeserializer 内部调用ContactDeserializer。
这样,当ContactController 接收“联系人 JSON”以创建新的 Contact 实例时,ContactDeserializer 将参与完成工作。 并且,如果 AccountController 接收“帐户 JSON”以创建新的 Account 实例,那么 AccountDeserializer 将参与完成这项工作...和 它还使用ContactDeserialzer 来处理帐户 JSON 的内部 contact 字段的反序列化。
可以这样做吗?! 一个 Jackson 解串器可以在其中重复使用其他解串器吗?如果是这样,怎么做?如果没有,那这里的解决方案是什么?!
【问题讨论】:
-
为什么不在对象映射器中注册之前通过构造函数将
ContactDeserializer注入到您的AccountDeserializer中?如果您查看 Jackson 源代码,您还会注意到在StdDeserializer的各种实现中都在做同样的事情。
标签: serialization spring-boot jackson