Trois façons d'utiliser Jackson pour JSON avec Java
Temps de lecture: 8 minutes
Si vous avez l'habitude d'un langage de type statique comme Java, travailler avec du JSON peut s'avérer difficile. JSON n'a pas de définition de type et ne dispose pas de certaines fonctionnalités qui pourraient être utiles : il n'y a que des chaînes de caractères, des nombres, des booléens et des null
. Alors, pour stocker d'autres types d'éléments (comme une date ou une heure), nous sommes obligés d'utiliser une convention basée sur des chaînes. Malgré ses lacunes, JSON est le format le plus courant pour les API sur le Web. Nous avons donc vraiment besoin de trouver un moyen de travailler avec dans Java.
Jackson est l'une des bibliothèques Java pour JSON les plus courantes et c'est celle que j'utilise le plus fréquemment. Pour cet article, j'ai choisi un document JSON assez complexe et trois requêtes que j'aimerais effectuer à l'aide de Jackson. Je vais comparer trois approches différentes :
- Modèle d'arborescence
- Liaison de données
- Requêtes de chemin d'accès
La totalité du code utilisé dans cet article se trouve dans ce répertoire. Il fonctionne avec Java 8 et ses versions ultérieures.
Autres bibliothèques Java pour travailler avec JSON
Pour travailler avec JSON, les librairies Java les plus populaires sont Jackson et Gson, selon les taux d'utilisation dans Maven Central et les notes GitHub. Dans cet article, je vais utiliser Jackson. Vous pouvez également vous référer à un article équivalent avec des exemples de code Gson ici.
Vous pouvez jeter un œil à des exemples de dépendance Jackson ici.
Exemples de données et de questions
Pour trouver des exemples de données, j'ai lu l'article que Tilde a écrit récemment : 7 API intéressantes dont vous ignoriez avoir besoin. J'ai choisi l'API de NeoWs (Near Earth Object Web Service ou service Web des objets proches de la Terre) parmi les API de la NASA. Cette API est gérée par l'équipe très brillamment nommée SpaceRocks.
La requête de l'API de flux NeoWs renvoie la liste de tous les astéroïdes qui se rapprocheront le plus de la Terre au cours des 7 prochains jours. Je vais vous montrer comment répondre aux questions suivantes dans Java :
- Combien y en a-t-il ?
Vous pouvez trouver cette information en examinant la cléelement_count
à la racine de l'objet JSON. - Combien d'entre eux sont potentiellement dangereux ?
Il faut faire une boucle avec chaque NEO (objet proche de la Terre) et vérifier la cléis_potentially_hazardous_asteroid
, qui est une valeur booléenne dans JSON. (Spoiler ! Ce n'est pas zéro.) - Quel est le nom et la vitesse de l'objet proche de la Terre le plus rapide ?
Encore une fois, nous devons faire une boucle, mais cette fois, les objets sont plus complexes. Nous devons également être conscients que les vitesses sont stockées sous forme de chaînes de caractères et non de chiffres, par exemple :"kilometers_per_second"
: "6.076659807". C'est assez courant dans les documents JSON car cela évite les problèmes de précision avec les nombres très petits ou très grands.
Un modèle d'arborescence JSON
Jackson vous permet de lire le JSON sous forme de modèle d'arborescence : des objets Java qui représentent des objets, des séries et des valeurs JSON. Ces objets peuvent s'appeler JsonNode
ou JsonArray
, et ils sont fournis par Jackson.
Avantages :
- Vous n'aurez pas besoin de créer vous-même des classes supplémentaires
- Jackson peut réaliser des contraintes de type implicite et explicite pour vous
Inconvénients :
- Votre code, qui opère avec les objets du modèle d'arborescence Jackson, peut être dense
- Il est très tentant de combiner le code Jackson à la logique de l'application, mais cela risque de compliquer la lecture et le test de votre code
Exemples de modèles d'arborescence Jackson
Jackson utilise une classe appelée ObjectMapper
comme point d'entrée principal. En général, je crée un nouvel ObjectMapper
au démarrage de l'application et comme les instances d'ObjectMapper sont thread-safe, on peut le traiter comme un singleton.
Un ObjectMapper
peut lire le code JSON à partir de diverses sources à l'aide d'une méthode readTree
surchargée. Ici, j'ai fourni une chaîne. readTree
renvoie un JsonNode
qui représente la racine du document JSON. Les instances JsonNode
peuvent être des JsonObjects
, des JsonArrays
ou toute une variété de nœuds de « valeur », comme TextNode
ou IntNode
.
Voici le code permettant d'analyser une chaîne JSON en JsonNode
:
[ce code dans le répertoire d'exemple]
Combien y a-t-il de NEO ?
Il faut trouver la clé element_count
dans le JsonNode et la renvoyer sous la forme int
. Le code se lit plutôt bien :
[ce code dans le répertoire d'exemple]
Gestion des erreurs : sans element_count
, .get("element_count")
renvoie null
et il y a une exception NullPointerException avec .asInt()
. Si vous utilisez .path
plutôt que .get
, Jackson peut utiliser le modèle d'objet Null pour éviter ce type d'exception. Dans les deux cas, vous devrez corriger les erreurs. Pour ma part, j'ai tendance à utiliser .get
.
Combien d'astéroïdes potentiellement dangereux y a-t-il cette semaine ?
Pour cette question, je m'attendais à ce que la réponse soit zéro. En réalité, c'est 19. Mais je ne panique pas pour autant (pour l'instant). Pour calculer cela à partir de la racine JsonNode
, nous devons :
- Itérer tous les NEO - il existe une liste de ces éléments pour chaque date, nous aurons donc besoin d'une boucle imbriquée
- Incrémenter un compteur si le champ
is_potentially_hazardous_asteroid
esttrue
.
Voici le code :
[ce code dans le répertoire d'exemple]
C'est un peu spécial : Jackson ne nous permet pas directement d'utiliser l'API Streams, donc j'ai recours à une boucle imbriquée. asBoolean
renvoie la valeur des champs booléens dans JSON, mais peut également être appelé sur d'autres types :
- les nœuds numériques (numeric nodes) seront résolus comme
true
s'ils n'étaient pas égaux à zéro - les nœuds de texte (text nodes) sont
true
si la valeur est"true"
Quel est le nom et la vitesse du NEO le plus rapide ?
La méthode de recherche et d'itération de chacun des NEO est la même que dans l'exemple précédent, mais la vitesse de chaque NEO est imbriquée sous plusieurs niveaux, vous devez donc les parcourir pour choisir la valeur kilometers_per_second
.
J'ai créé une petite classe appelée NeoNameAndSpeed
pour contenir les deux valeurs. Cela peut devenir un record
par la suite. Le code crée l'un de ces objets de la manière suivante :
[ce code dans le répertoire d'exemple]
Même si les vitesses sont stockées sous forme de chaînes dans JSON, je pourrais appeler .asDouble()
- Jackson est suffisamment intelligent pour appeler Double.parseDouble
à ma place.
Liaison de données (data biding) JSON aux classes personnalisées
Si vous avez des requêtes de données plus complexes, ou si vous devez créer des objets à partir de JSON pour les transmettre à un autre code, le modèle d'arborescence n'est pas adapté. Jackson propose un autre mode de fonctionnement appelé liaison de données, qui permet de décomposer directement JSON en objets de votre propre design. Par défaut, Spring MVC utilise Jackson de cette manière lorsque vous acceptez ou renvoyez des objets à partir de vos contrôleurs Web.
Avantages :
- La conversion JSON en objets est simple
- La lecture de valeurs à partir d'objets peut utiliser n'importe quelle API Java
- Les objets sont indépendants de Jackson et peuvent donc être utilisés dans d'autres contextes
- Le mapping est personnalisable avec des Modules Jackson
Inconvénients :
- Travail en amont : vous devez créer des classes dont la structure correspond aux objets JSON, puis demander à Jackson de lire votre JSON dans ces objets
Introduction à la liaison de données (data binding)
Voici un exemple simple basé sur un petit sous-ensemble de NEO JSON :
Imaginons une classe capable de contenir les données comme suit :
Jackson est presque prêt à l'emploi pour mapper des objets JSON et faire correspondre des objets similaires. Il gère assez bien le fait que l'objet int id
soit une chaîne, mais il a besoin d'aide pour convertir la chaîne 2020-04-12
en objet LocalDate
. Il réalise cela à l'aide d'un module personnalisé, qui définit un mappage JSON pour les types d'objets personnalisés.
Liaison de données Jackson : styles personnalisés
Pour le mapping LocalDate
, Jackson fournit une dépendance. Ajoutez ceci à votre projet et configurez votre ObjectMapper
comme suit :
[ce code dans le répertoire d'exemple]
Liaison de données Jackson : noms de champs personnalisés
Vous avez peut-être remarqué que j'ai utilisé closeApproachDate
dans mon exemple JSON ci-dessus, alors que les données de la NASA utilisent close_approach_date
. J'ai fait cela parce que Jackson utilisera les capacités de réflexion de Java pour faire correspondre les clés JSON aux noms de champ Java (et ils doivent parfaitement correspondre).
La plupart du temps, vous ne pouvez pas modifier votre JSON : il provient généralement d'une API que vous ne contrôlez pas. Mais vous voulez tout de même éviter que des champs soient écrits en snake_case
dans vos classes Java. C'est réalisable grâce à une annotation sur le champ closeApproachDate
:
[ce code dans le répertoire d'exemple]
Création d'objets personnalisés avec JsonSchema2Pojo
Vous êtes probablement en train de vous dire que tout cela demande beaucoup de temps. Changement de noms de champ, lecture et écriture personnalisées… Sans oublier le nombre de classes que vous devrez peut-être créer. Eh bien, vous avez raison ! Mais ne vous inquiétez pas, il existe un excellent outil capable de créer les classes à votre place.
JsonSchema2Pojo peut se baser sur un schéma JSON ou, plus utile encore, sur un document JSON afin de générer des classes correspondantes pour vous. Il comprend les annotations de Jackson et propose de nombreuses options, même si les valeurs par défaut sont déjà bien adaptées. En général, il fait 90 % de mon travail à ma place, mais les classes ont souvent besoin d'être peaufinées une fois générées.
Pour l'utiliser pour ce projet, j'ai supprimé tous les NEO sauf un, et j'ai sélectionné les options suivantes :
[code généré dans le répertoire d'exemple]
Données stockées dans les clés et les valeurs
Le JSON de NeoWS possède une fonctionnalité un peu particulière (mais pas inhabituelle) : certaines données sont stockées dans les clés plutôt que dans les valeurs des objets JSON. Le mappage near_earth_objects
comporte des clés qui sont des dates. Cela pose un problème, car les dates ne seront pas toujours les mêmes, et bien sûr, jsonschema2pojo ne le sait pas. Il a créé un champ appelé _20200412
. Pour résoudre ce problème, j'ai changé le nom de la classe _20200412
en NeoDetails
et le type de nearEarthObjects
est devenu Map<String, List<NeoDetails>>
(à consulter ici). Je peux alors supprimer la classe NearEarthObjects
qui n'est plus utilisée.
J'ai également modifié les types de numbers-in-strings de String
en double
et j'ai ajouté LocalDate
là où c'était nécessaire.
Liaison de données Jackson pour l'API NEO
Avec les classes générées par JsonSchema2Pojo, l'ensemble du volumineux document JSON peut être lu avec :
Trouver les données voulues
Maintenant que nous disposons d'anciens objets Java simples, nous pouvons tirer parti de l'accès aux champs et de l'API Streams pour trouver les données dont nous avons besoin :
[ce code dans le répertoire d'exemple]
Ce code est plus compatible avec le langage Java, et il n'y a pas du tout de Jackson dedans, ce qui facilite les tests individuels de cette version. Si vous travaillez souvent avec le même format JSON, l'investissement dans la création de classes en vaut probablement la peine.
Requêtes de chemin d'accès depuis JSON à l'aide de JsonPointer
Avec Jackson, vous pouvez également utiliser un pointeur JSON. C'est un moyen compact de faire référence à une valeur unique spécifique dans un document JSON :
[ce code dans le répertoire d'exemple]
Les pointeurs JSON ne peuvent pointer que vers une seule valeur. Vous ne pouvez pas agréger ou utiliser des caractères génériques, ils sont donc plutôt limités.
Résumé des différentes façons d'utiliser Jackson
Pour les requêtes simples, le modèle d'arborescence peut vous être très utile, mais vous devrez probablement combiner l'analyse JSON et la logique d'application, ce qui peut rendre le test et la maintenance plus compliqués.
Pour extraire une valeur unique d'un document JSON, vous pouvez envisager d'utiliser un pointeur JSON, mais le code est à peine plus simple que le modèle d'arborescence, alors je ne le fais presque jamais.
Pour les requêtes plus complexes, et en particulier lorsque votre analyse JSON fait partie d'une application plus importante, je recommande la liaison de données. C'est généralement plus facile à long terme, puisque JsonSchema2Pojo se charge d'une bonne partie du travail pour vous.
Quelles sont vos méthodes de travail préférées avec JSON pour Java ? Partagez-les sur mon Twitter @MaximumGilliard, ou par e-mail à mgilliard@twilio.com. J'ai hâte de voir ce que {"you": "build"}
.
Articles associés
Ressources connexes
Twilio Docs
Des API aux SDK en passant par les exemples d'applications
Documentation de référence sur l'API, SDK, bibliothèques d'assistance, démarrages rapides et didacticiels pour votre langage et votre plateforme.
Centre de ressources
Les derniers ebooks, rapports de l'industrie et webinaires
Apprenez des experts en engagement client pour améliorer votre propre communication.
Ahoy
Le hub de la communauté des développeurs de Twilio
Meilleures pratiques, exemples de code et inspiration pour créer des expériences de communication et d'engagement numérique.