在其他反序列化程序内调用自定义 Jackson 反序列化程序



我正在编写一个使用Jackson进行序列化的Spring Boot应用程序(RESTful webservice)。我有以下数据模型,这些数据模型将在服务及其 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.
}

因此,将有一些端点用于 CRUDdingAccounts,其他端点用于 CRUDdingContacts,等等。例如,AccountController将公开 CRUDdingAccount实例的端点:

@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,以便创建新的AccountContact等实例。同时,这些数据模型上有一些字段,我不想向客户端公开。类似于BaseEntity#id的东西(这是数据库中实体的PK)。或者例如,在State的情况下,我只是希望客户端了解(并使用)abbrev字段等。我不希望他们看到其他BaseLookup领域,甚至不知道他们。

因此,我的最终目标是允许客户端发布以下 JSON,并让自定义 Jackson 反序列化程序将该 JSON 转换为Account实例:

{
"contact" : {
"givenName" : "Him",
"surname" : "Himself",
"state" : "NY"
},
"code" : "12345"
}

所以你看,就像我上面说的,这个JSON完成了几件事:

  1. 客户端在 POST 创建新实例时不提供BaseEntity#idBaseEntity#refId
  2. 对于contact.state字段,这是一个具有多个字段(idrefIdnamelabeldescriptionabbrev)的BaseLookup,用户只需要提供abbrev字段,反序列化程序应该找出客户端引用的State
  3. Account类实际上还有许多其他在服务器端推断/生成的字段;客户端不需要知道它们就可以创建一个Account实例。
  4. 上面的JSON是如果我们使用Jackson的默认行为序列化Account将得到的简化形式;这是为了使客户端的事情更容易,在服务器端甚至更安全(不公开PK等)。

这里需要注意的重要一点是,发送到此控制器的contact字段的 JSON 与将 POST 到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(用于 CRUDdingContact实例,而不考虑关联的Account),并且因为我有类似的愿望从客户端隐藏Contact字段以及简化进入此ContactController#createContact端点的JSON,因此除了此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字段的反序列化。

这能做到吗?!一个杰克逊反序列化器可以在其中重用其他反序列化器吗?如果是这样,如何?如果没有,那么这里的解决方案是什么?!

您可以通过调用ObjectCodectreeToValue方法来调用ContactDeserializer。如果您已在ObjectMapper上注册了ContactDeserializer,杰克逊将自动为您领取。

public class AccountDeserializer extends JsonDeserializer<Account> {
@Override
public Account deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonNode node = p.readValueAsTree();
JsonNode contactNode = node.get("contact");
Contact contact = null;
if (contactNode != null) {
contact = p.getCodec().treeToValue(contactNode, Contact.class);
}
return new Account(contact, /* account properties */);
}
}

编辑

如果要将反序列化程序添加到由 Spring Boot 创建的现有映射器中,可以在其中一个配置类中自动连接它并根据需要进行自定义。

@Configuration
public class ObjectMapperConfiguration {
@Autowired
public void configureObjectMapper(ObjectMapper mapper) {
SimpleModule module = new SimpleModule()
.addDeserializer(Account.class, new AccountDeserializer())
.addDeserializer(Contact.class, new ContactDeserializer());
mapper.registerModule(module);
}
}

最新更新