Autres annotations de Jackson

1. Vue d'ensemble

Cet article couvre certaines annotations supplémentaires qui n'étaient pas couvertes dans l'article précédent, A Guide to Jackson Annotations - nous en passerons en revue sept.

2. @JsonIdentityReference

@JsonIdentityReference est utilisé pour la personnalisation des références aux objets qui seront sérialisés en tant qu'identités d'objet au lieu de POJO complets. Il fonctionne en collaboration avec @JsonIdentityInfo pour forcer l'utilisation des identités d'objet dans chaque sérialisation, différent de tout sauf la première fois lorsque @JsonIdentityReference est absent. Ce couple d'annotations est particulièrement utile pour traiter les dépendances circulaires entre les objets. Veuillez consulter la section 4 de l'article Jackson - Relation bidirectionnelle pour plus d'informations.

Afin de démontrer l'utilisation de @JsonIdentityReference , nous allons définir deux classes de bean différentes, sans et avec cette annotation.

Le bean sans @JsonIdentityReference :

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class BeanWithoutIdentityReference { private int id; private String name; // constructor, getters and setters }

Pour le bean utilisant @JsonIdentityReference , nous choisissons la propriété id comme identité de l'objet:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") @JsonIdentityReference(alwaysAsId = true) public class BeanWithIdentityReference { private int id; private String name; // constructor, getters and setters }

Dans le premier cas, où @JsonIdentityReference est absent, ce bean est sérialisé avec tous les détails sur ses propriétés:

BeanWithoutIdentityReference bean = new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation"); String jsonString = mapper.writeValueAsString(bean);

La sortie de la sérialisation ci-dessus:

{ "id": 1, "name": "Bean Without Identity Reference Annotation" }

Lorsque @JsonIdentityReference est utilisé, le bean est sérialisé comme une identité simple à la place:

BeanWithIdentityReference bean = new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation"); String jsonString = mapper.writeValueAsString(bean); assertEquals("1", jsonString);

3. @JsonAppend

L' annotation @JsonAppend est utilisée pour ajouter des propriétés virtuelles à un objet en plus des propriétés normales lorsque cet objet est sérialisé. Cela est nécessaire lorsque nous voulons ajouter des informations supplémentaires directement dans une chaîne JSON, plutôt que de modifier la définition de classe. Par exemple, il peut être plus pratique d'insérer les métadonnées de version d'un bean dans le document JSON correspondant que de lui fournir une propriété supplémentaire.

Supposons que nous ayons un bean sans @JsonAppend comme suit:

public class BeanWithoutAppend { private int id; private String name; // constructor, getters and setters }

Un test confirmera qu'en l'absence de l' annotation @JsonAppend , la sortie de sérialisation ne contient pas d'informations sur la propriété de version supplémentaire , malgré le fait que nous essayons d'ajouter à l' objet ObjectWriter :

BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation"); ObjectWriter writer = mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0"); String jsonString = writer.writeValueAsString(bean);

La sortie de sérialisation:

{ "id": 2, "name": "Bean Without Append Annotation" }

Maintenant, disons que nous avons un bean annoté avec @JsonAppend :

@JsonAppend(attrs = { @JsonAppend.Attr(value = "version") }) public class BeanWithAppend { private int id; private String name; // constructor, getters and setters }

Un test similaire au précédent vérifiera que lorsque l' annotation @JsonAppend est appliquée, la propriété supplémentaire est incluse après la sérialisation:

BeanWithAppend bean = new BeanWithAppend(2, "Bean With Append Annotation"); ObjectWriter writer = mapper.writerFor(BeanWithAppend.class).withAttribute("version", "1.0"); String jsonString = writer.writeValueAsString(bean);

La sortie de cette sérialisation montre que la propriété version a été ajoutée:

{ "id": 2, "name": "Bean With Append Annotation", "version": "1.0" }

4. @JsonNaming

L' annotation @JsonNaming est utilisée pour choisir les stratégies de dénomination des propriétés dans la sérialisation, en remplaçant la valeur par défaut. En utilisant l' élément value , nous pouvons spécifier n'importe quelle stratégie, y compris les stratégies personnalisées.

En plus de la valeur par défaut, qui est LOWER_CAMEL_CASE (par exemple, lowerCamelCase ), la bibliothèque Jackson nous fournit quatre autres stratégies de dénomination de propriété intégrées pour plus de commodité:

  • KEBAB_CASE : Les éléments de nom sont séparés par des tirets, par exemple kebab-case .
  • LOWER_CASE : Toutes les lettres sont en minuscules sans séparateurs, par exemple en minuscules .
  • SNAKE_CASE : Toutes les lettres sont en minuscules avec des traits de soulignement comme séparateurs entre les éléments du nom, par exemple snake_case .
  • UPPER_CAMEL_CASE : Tous les éléments de nom, y compris le premier, commencent par une lettre majuscule, suivie de minuscules et il n'y a pas de séparateurs, par exemple UpperCamelCase .

Cet exemple illustre la manière de sérialiser des propriétés à l'aide de noms de cas de serpent, où une propriété nommée beanName est sérialisée en tant que nom_bean.

Étant donné une définition de haricot:

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) public class NamingBean { private int id; private String beanName; // constructor, getters and setters }

Le test ci-dessous démontre que la règle de dénomination spécifiée fonctionne comme requis:

NamingBean bean = new NamingBean(3, "Naming Bean"); String jsonString = mapper.writeValueAsString(bean); assertThat(jsonString, containsString("bean_name"));

La variable jsonString contient les données suivantes:

{ "id": 3, "bean_name": "Naming Bean" }

5. @JsonPropertyDescription

La bibliothèque Jackson est capable de créer des schémas JSON pour les types Java à l'aide d'un module séparé appelé JSON Schema. Le schéma est utile lorsque nous voulons spécifier la sortie attendue lors de la sérialisation d'objets Java, ou pour valider un document JSON avant la désérialisation.

L' annotation @JsonPropertyDescription permet d' ajouter une description lisible par l'homme au schéma JSON créé en fournissant le champ de description .

Cette section utilise le bean déclaré ci-dessous pour démontrer les capacités de @JsonPropertyDescription :

public class PropertyDescriptionBean { private int id; @JsonPropertyDescription("This is a description of the name property") private String name; // getters and setters }

La méthode de génération d'un schéma JSON avec l'ajout du champ de description est indiquée ci-dessous:

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"));

Comme nous pouvons le voir, la génération du schéma JSON a réussi:

{ "type": "object", "id": "urn:jsonschema:com:baeldung:jackson:annotation:extra:PropertyDescriptionBean", "properties": { "name": { "type": "string", "description": "This is a description of the name property" }, "id": { "type": "integer" } } }

6. @JsonPOJOBuilder

L' annotation @JsonPOJOBuilder est utilisée pour configurer une classe de générateur pour personnaliser la désérialisation d'un document JSON afin de récupérer les POJO lorsque la convention de dénomination est différente de la convention par défaut.

Supposons que nous devions désérialiser la chaîne JSON suivante:

{ "id": 5, "name": "POJO Builder Bean" }

Cette source JSON sera utilisée pour créer une instance de POJOBuilderBean :

@JsonDeserialize(builder = BeanBuilder.class) public class POJOBuilderBean { private int identity; private String beanName; // constructor, getters and setters }

Les noms des propriétés du bean sont différents de ceux des champs de la chaîne JSON. C'est là que @JsonPOJOBuilder vient à la rescousse.

L' annotation @JsonPOJOBuilder est accompagnée de deux propriétés:

  • buildMethodName : Le nom de la méthode sans argument utilisée pour instancier le bean attendu après avoir lié les champs JSON aux propriétés de ce bean. Le nom par défaut est build .
  • withPrefix : Le préfixe de nom pour la détection automatique de la correspondance entre le JSON et les propriétés du bean. Le préfixe par défaut est avec .

Cet exemple utilise la classe BeanBuilder ci-dessous, qui est utilisée sur POJOBuilderBean :

@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); } }

Dans le code ci-dessus, nous avons configuré le @JsonPOJOBuilder pour utiliser une méthode de construction appelée createBean et le préfixe de construction pour les propriétés correspondantes.

L'application de @JsonPOJOBuilder à un bean est décrite et testée comme suit:

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());

Le résultat montre qu'un nouvel objet de données a été recréé avec succès à partir d'une source JSON malgré une incompatibilité dans les noms des propriétés.

7. @JsonTypeId

L' annotation @JsonTypeId est utilisée pour indiquer que la propriété annotée doit être sérialisée en tant qu'ID de type lors de l'inclusion d'informations de type polymorphe, plutôt qu'en tant que propriété normale. Ces métadonnées polymorphes sont utilisées lors de la désérialisation pour recréer des objets des mêmes sous-types qu'avant la sérialisation, plutôt que des supertypes déclarés.

Pour plus d'informations sur la gestion de l'héritage par Jackson, consultez la section 2 de l'héritage dans Jackson.

Disons que nous avons une définition de classe de bean comme suit:

public class TypeIdBean { private int id; @JsonTypeId private String name; // constructor, getters and setters }

The following test validates that @JsonTypeId works as it is meant to:

mapper.enableDefaultTyping(DefaultTyping.NON_FINAL); TypeIdBean bean = new TypeIdBean(6, "Type Id Bean"); String jsonString = mapper.writeValueAsString(bean); assertThat(jsonString, containsString("Type Id Bean"));

The serialization process' output:

[ "Type Id Bean", { "id": 6 } ]

8. @JsonTypeIdResolver

The @JsonTypeIdResolver annotation is used to signify a custom type identity handler in serialization and deserialization. That handler is responsible for conversion between Java types and type id included in a JSON document.

Suppose that we want to embed type information in a JSON string when dealing with the following class hierarchy.

The AbstractBean superclass:

@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 }

The FirstBean subclass:

public class FirstBean extends AbstractBean { String firstName; public FirstBean(int id, String name) { super(id); setFirstName(name); } // no-arg constructor, getter and setter }

The LastBean subclass:

public class LastBean extends AbstractBean { String lastName; public LastBean(int id, String name) { super(id); setLastName(name); } // no-arg constructor, getter and setter }

Instances of those classes are used to populate a BeanContainer object:

public class BeanContainer { private List beans; // getter and setter }

We can see that the AbstractBean class is annotated with @JsonTypeIdResolver, indicating that it uses a custom TypeIdResolver to decide how to include subtype information in serialization and how to make use of that metadata the other way round.

Here is the resolver class to handle inclusion of type information:

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); } }

The two most notable methods are idFromValueAndType and typeFromId, with the former telling the way to include type information when serializing POJOs and the latter determining the subtypes of re-created objects using that metadata.

In order to make sure that both serialization and deserialization work well, let's write a test to validate the complete progress.

Tout d'abord, nous devons instancier un conteneur de bean et des classes de bean, puis remplir ce conteneur avec des instances 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);

Ensuite, l' objet BeanContainer est sérialisé et nous confirmons que la chaîne résultante contient des informations de type:

String jsonString = mapper.writeValueAsString(serializedContainer); assertThat(jsonString, containsString("bean1")); assertThat(jsonString, containsString("bean2"));

La sortie de la sérialisation est indiquée ci-dessous:

{ "beans": [ { "@type": "bean1", "id": 1, "firstName": "Bean 1" }, { "@type": "bean2", "id": 2, "lastName": "Bean 2" } ] }

Cette structure JSON sera utilisée pour recréer des objets des mêmes sous-types qu'avant la sérialisation. Voici les étapes de mise en œuvre pour la désérialisation:

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. Conclusion

Ce didacticiel a expliqué en détail plusieurs annotations Jackson moins courantes. L'implémentation de ces exemples et extraits de code se trouve dans un projet GitHub.