Mais anotações de Jackson
1. Visão geral
Este artigo cobre algumas anotações adicionais que não foram abordadas no artigo anterior,A Guide to Jackson Annotations - examinaremos sete delas.
2. @JsonIdentityReference
@JsonIdentityReference é usado para customização de referências a objetos que serão serializados como identidades de objeto em vez de POJOs completos. Ele funciona em colaboração com@JsonIdentityInfo para forçar o uso de identidades de objeto em cada serialização, diferente de todos, exceto na primeira vez, quando@JsonIdentityReference está ausente. Essas anotações são mais úteis ao lidar com dependências circulares entre objetos. Consulte a seção 4 do artigoJackson – Bidirectional Relationship para obter mais informações.
Para demonstrar o uso de@JsonIdentityReference, definiremos duas classes de bean diferentes, sem e com esta anotação.
O feijão sem@JsonIdentityReference:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class BeanWithoutIdentityReference {
private int id;
private String name;
// constructor, getters and setters
}
Para o bean usando@JsonIdentityReference, escolhemos a propriedadeid para ser a identidade do objeto:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
public class BeanWithIdentityReference {
private int id;
private String name;
// constructor, getters and setters
}
No primeiro caso, onde@JsonIdentityReference está ausente, esse bean é serializado com todos os detalhes de suas propriedades:
BeanWithoutIdentityReference bean
= new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
A saída da serialização acima:
{
"id": 1,
"name": "Bean Without Identity Reference Annotation"
}
Quando@JsonIdentityReference é usado, o bean é serializado como uma identidade simples ao invés:
BeanWithIdentityReference bean
= new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
assertEquals("1", jsonString);
3. @JsonAppend
A anotação@JsonAppend é usada para adicionar propriedades virtuais a um objeto, além das normais, quando o objeto é serializado. Isso é necessário quando queremos adicionar informações suplementares diretamente a uma string JSON, em vez de alterar a definição de classe. Por exemplo, pode ser mais conveniente inserir os metadadosversion de um bean no documento JSON correspondente do que fornecer a ele uma propriedade adicional.
Suponha que temos um bean sem@JsonAppend da seguinte forma:
public class BeanWithoutAppend {
private int id;
private String name;
// constructor, getters and setters
}
Um teste irá confirmar que, na ausência da anotação@JsonAppend, a saída de serialização não contém informações sobre a propriedadeversion suplementar, apesar do fato de tentarmos adicionar ao objetoObjectWriter :
BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation");
ObjectWriter writer
= mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);
A saída de serialização:
{
"id": 2,
"name": "Bean Without Append Annotation"
}
Agora, digamos que temos um bean anotado com@JsonAppend:
@JsonAppend(attrs = {
@JsonAppend.Attr(value = "version")
})
public class BeanWithAppend {
private int id;
private String name;
// constructor, getters and setters
}
Um teste semelhante ao anterior verificará que quando a anotação@JsonAppend é aplicada, a propriedade suplementar é incluída após a serialização:
BeanWithAppend bean = new BeanWithAppend(2, "Bean With Append Annotation");
ObjectWriter writer
= mapper.writerFor(BeanWithAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);
A saída dessa serialização mostra que a propriedadeversion foi adicionada:
{
"id": 2,
"name": "Bean With Append Annotation",
"version": "1.0"
}
4. @JsonNaming
A anotação@JsonNaming é usada para escolher as estratégias de nomenclatura para propriedades em serialização, substituindo o padrão. Usando o elementovalue, podemos especificar qualquer estratégia, incluindo as personalizadas.
Além do padrão, que éLOWER_CAMEL_CASE (por exemplo lowerCamelCase), a biblioteca Jackson fornece quatro outras estratégias de nomenclatura de propriedade integradas para sua conveniência:
-
KEBAB_CASE: os elementos de nome são separados por hifens, por exemplo kebab-case.
-
LOWER_CASE: todas as letras são minúsculas sem separadores, por exemplo, lowercase.
-
SNAKE_CASE: todas as letras são minúsculas com sublinhados como separadores entre elementos de nome, por exemplo, snake_case.
-
UPPER_CAMEL_CASE: todos os elementos de nome, incluindo o primeiro, começam com uma letra maiúscula, seguida por letras minúsculas e não há separadores, por exemplo, UpperCamelCase.
Este exemplo ilustrará a maneira de serializar propriedades usando nomes de caso de cobra, onde uma propriedade chamadabeanName é serializada comobean_name.
Dada uma definição de bean:
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class NamingBean {
private int id;
private String beanName;
// constructor, getters and setters
}
O teste abaixo demonstra que a regra de nomenclatura especificada funciona conforme necessário:
NamingBean bean = new NamingBean(3, "Naming Bean");
String jsonString = mapper.writeValueAsString(bean);
assertThat(jsonString, containsString("bean_name"));
A variáveljsonString contém os seguintes dados:
{
"id": 3,
"bean_name": "Naming Bean"
}
5. @JsonPropertyDescription
A biblioteca Jackson é capaz de criar esquemas JSON para tipos Java com a ajuda de um módulo separado chamadoJSON Schema. O esquema é útil quando queremos especificar a saída esperada ao serializar objetos Java ou validar um documento JSON antes da desserialização.
A anotação@JsonPropertyDescription permite que uma descrição legível por humanos seja adicionada ao esquema JSON criado, fornecendo o campodescription.
Esta seção usa o bean declarado abaixo para demonstrar os recursos de@JsonPropertyDescription:
public class PropertyDescriptionBean {
private int id;
@JsonPropertyDescription("This is a description of the name property")
private String name;
// getters and setters
}
O método para gerar um esquema JSON com a adição do campodescription é mostrado abaixo:
SchemaFactoryWrapper wrapper = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(PropertyDescriptionBean.class, wrapper);
JsonSchema jsonSchema = wrapper.finalSchema();
String jsonString = mapper.writeValueAsString(jsonSchema);
assertThat(jsonString, containsString("This is a description of the name property"));
Como podemos ver, a geração do esquema JSON foi bem-sucedida:
{
"type": "object",
"id": "urn:jsonschema:com:example:jackson:annotation:extra:PropertyDescriptionBean",
"properties":
{
"name":
{
"type": "string",
"description": "This is a description of the name property"
},
"id":
{
"type": "integer"
}
}
}
6. @JsonPOJOBuilder
A anotação@JsonPOJOBuilder é usada para configurar uma classe de construtor para customizar a desserialização de um documento JSON para recuperar POJOs quando a convenção de nomenclatura é diferente do padrão.
Suponha que precisamos desserializar a seguinte string JSON:
{
"id": 5,
"name": "POJO Builder Bean"
}
Essa fonte JSON será usada para criar uma instância doPOJOBuilderBean:
@JsonDeserialize(builder = BeanBuilder.class)
public class POJOBuilderBean {
private int identity;
private String beanName;
// constructor, getters and setters
}
Os nomes das propriedades do bean são diferentes daqueles dos campos na string JSON. É aqui que@JsonPOJOBuilder vem ao resgate.
A anotação@JsonPOJOBuilder é acompanhada por duas propriedades:
-
buildMethodName: o nome do método sem arg usado para instanciar o bean esperado após vincular campos JSON às propriedades desse bean. O nome padrão ébuild.
-
withPrefix: o prefixo do nome para a detecção automática de correspondência entre o JSON e as propriedades do bean. O prefixo padrão éwith.
Este exemplo usa a classeBeanBuilder abaixo, que é usada emPOJOBuilderBean:
@JsonPOJOBuilder(buildMethodName = "createBean", withPrefix = "construct")
public class BeanBuilder {
private int idValue;
private String nameValue;
public BeanBuilder constructId(int id) {
idValue = id;
return this;
}
public BeanBuilder constructName(String name) {
nameValue = name;
return this;
}
public POJOBuilderBean createBean() {
return new POJOBuilderBean(idValue, nameValue);
}
}
No código acima, configuramos@JsonPOJOBuilder para usar um método de construção chamadocreateBeane o prefixoconstruct para propriedades correspondentes.
A aplicação de@JsonPOJOBuilder a um feijão é descrita e testada da seguinte forma:
String jsonString = "{\"id\":5,\"name\":\"POJO Builder Bean\"}";
POJOBuilderBean bean = mapper.readValue(jsonString, POJOBuilderBean.class);
assertEquals(5, bean.getIdentity());
assertEquals("POJO Builder Bean", bean.getBeanName());
O resultado mostra que um novo objeto de dados foi recriado com êxito a partir de uma origem JSON, apesar de uma incompatibilidade nos nomes das propriedades.
7. @JsonTypeId
A anotação@JsonTypeId é usada para indicar que a propriedade anotada deve ser serializada como o id de tipo ao incluir informações de tipo polimórfico, em vez de uma propriedade regular. Esses metadados polimórficos são usados durante a desserialização para recriar objetos dos mesmos subtipos que eram antes da serialização, em vez dos supertipos declarados.
Para obter mais informações sobre o tratamento de herança de Jackson, consulte a seção 2 doInheritance in Jackson.
Digamos que temos uma definição de classe de bean da seguinte maneira:
public class TypeIdBean {
private int id;
@JsonTypeId
private String name;
// constructor, getters and setters
}
O teste a seguir valida que@JsonTypeId funciona como deveria:
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
TypeIdBean bean = new TypeIdBean(6, "Type Id Bean");
String jsonString = mapper.writeValueAsString(bean);
assertThat(jsonString, containsString("Type Id Bean"));
Saída do processo de serialização:
[
"Type Id Bean",
{
"id": 6
}
]
8. @JsonTypeIdResolver
A anotação@JsonTypeIdResolver é usada para significar um manipulador de identidade de tipo customizado na serialização e desserialização. Esse manipulador é responsável pela conversão entre os tipos Java e o ID do tipo incluído em um documento JSON.
Suponha que desejamos incorporar informações de tipo em uma string JSON ao lidar com a seguinte hierarquia de classes.
A superclasseAbstractBean:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "@type"
)
@JsonTypeIdResolver(BeanIdResolver.class)
public class AbstractBean {
private int id;
protected AbstractBean(int id) {
this.id = id;
}
// no-arg constructor, getter and setter
}
A subclasseFirstBean:
public class FirstBean extends AbstractBean {
String firstName;
public FirstBean(int id, String name) {
super(id);
setFirstName(name);
}
// no-arg constructor, getter and setter
}
A subclasseLastBean:
public class LastBean extends AbstractBean {
String lastName;
public LastBean(int id, String name) {
super(id);
setLastName(name);
}
// no-arg constructor, getter and setter
}
As instâncias dessas classes são usadas para preencher um objetoBeanContainer:
public class BeanContainer {
private List beans;
// getter and setter
}
Podemos ver que a classeAbstractBean é anotada com@JsonTypeIdResolver, indicando que ela usa umTypeIdResolver personalizado para decidir como incluir informações de subtipo na serialização e como usar esses metadados do outro caminho de volta.
Aqui está a classe resolvedor para lidar com a inclusão de informações de tipo:
public class BeanIdResolver extends TypeIdResolverBase {
private JavaType superType;
@Override
public void init(JavaType baseType) {
superType = baseType;
}
@Override
public Id getMechanism() {
return Id.NAME;
}
@Override
public String idFromValue(Object obj) {
return idFromValueAndType(obj, obj.getClass());
}
@Override
public String idFromValueAndType(Object obj, Class> subType) {
String typeId = null;
switch (subType.getSimpleName()) {
case "FirstBean":
typeId = "bean1";
break;
case "LastBean":
typeId = "bean2";
}
return typeId;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class> subType = null;
switch (id) {
case "bean1":
subType = FirstBean.class;
break;
case "bean2":
subType = LastBean.class;
}
return context.constructSpecializedType(superType, subType);
}
}
Os dois métodos mais notáveis sãoidFromValueAndTypeetypeFromId, com o primeiro informando a maneira de incluir informações de tipo ao serializar POJOs e o último determinando os subtipos de objetos recriados usando esses metadados.
Para garantir que a serialização e a desserialização funcionem bem, vamos escrever um teste para validar o progresso completo.
Primeiro, precisamos instanciar um contêiner de bean e classes de bean e, em seguida, preencher esse contêiner com instâncias de bean:
FirstBean bean1 = new FirstBean(1, "Bean 1");
LastBean bean2 = new LastBean(2, "Bean 2");
List beans = new ArrayList<>();
beans.add(bean1);
beans.add(bean2);
BeanContainer serializedContainer = new BeanContainer();
serializedContainer.setBeans(beans);
Em seguida, o objetoBeanContainer é serializado e confirmamos que a string resultante contém informações de tipo:
String jsonString = mapper.writeValueAsString(serializedContainer);
assertThat(jsonString, containsString("bean1"));
assertThat(jsonString, containsString("bean2"));
A saída da serialização é mostrada abaixo:
{
"beans":
[
{
"@type": "bean1",
"id": 1,
"firstName": "Bean 1"
},
{
"@type": "bean2",
"id": 2,
"lastName": "Bean 2"
}
]
}
Essa estrutura JSON será usada para recriar objetos dos mesmos subtipos de antes da serialização. Aqui estão as etapas de implementação para desserialização:
BeanContainer deserializedContainer = mapper.readValue(jsonString, BeanContainer.class);
List beanList = deserializedContainer.getBeans();
assertThat(beanList.get(0), instanceOf(FirstBean.class));
assertThat(beanList.get(1), instanceOf(LastBean.class));
9. Conclusão
Este tutorial explicou várias anotações menos comuns de Jackson em detalhes. A implementação desses exemplos e trechos de código pode ser encontrada ema GitHub project.