Comparatif des librairies JSON

A l’occasion d’un récent projet, j’ai fait un tour des solutions de marshalling JSON, de Sojo à Jackson, en passant par FlexJSON et Jettison. Partant de mes besoins, je vous propose de suivre ce voyage au coeur des API JSON.
Il me faut une API:

  1. Capable de sérialiser et désérialiser des arbres JSON sans adhérence aux beans modèles.
  2. Pouvant travailler directement sur des flux.
  3. Capable de tenir une charge conséquente, donc stable et performante.
  4. Avec le minimum possible de dépendances.

Dans mon contexte, Sojo est la solution en place pour convertir des beans Java en chaîne JSON et inversement. C’est donc logiquement par Sojo que commence ce voyage.

Sojo

La librairie fournit une interface simple avec des Serializer pour convertir des objets en XML, CSV ou JSON. Pour assurer la conversion des données avec un formatage particulier comme les Date, Sojo permet de définir un ou plusieurs Converter. Des filtres peuvent aussi être utilisés lors de la sérialisation pour ignorer les propriétés du bean qui ne doivent pas être ajoutées à la chaîne.
Voilà pour les points forts, voyons maintenant les faiblesses de Sojo. Bien qu’utilisant Maven, l’artifact du projet n’est disponible sur aucun des dépôts standards. Il faut donc l’ajouter manuellement dans un dépôt privé ou local. Heureusement, il s’agit d’un simple Jar sans dépendance. Tant pis pour les mises à jour, de toute façon à y regarder de plus près le projet est inactif depuis longtemps.
Vient maintenant un point un peu plus désagréable : les Serializer ne fonctionnent que sur des String, impossible de travailler en direct sur des flux. Ce bête passage obligé a un impact assez important sur les performances puisque l’application va perdre du temps à créer les chaînes de caractères pour les lire ou les écrire ensuite sur un fichier.
Pour charger l’arbre JSON sans adhérence aux beans, il suffit de définir la classe Map en lieu et place de celle du bean jsonSerializer.deserialize(chaine, Map.class). Cela paraît simple, mais il faut le savoir, car la documentation ne vous le dira pas. Attention à bien exclure la propriété ‘class’ à la sérialisation jsonSerializer.serialize(bean, new String()("class"));, sans quoi vous obtiendrez toujours votre bean d’origine.
L’API fournit en prime des outils pour parcourir un graphe d’objets, manipuler des collections, comparer des objets, etc. Je cherche une API uniquement pour le marshalling JSON, et Sojo semble plus adaptée pour fournir une API de manipulation de Pojo que pour répondre à mon besoin.
La documentation est mince et la JavaDoc pratiquement vide. Bref, plus le temps passe, moins je pense que Sojo est la bonne solution pour le projet.

Puisque le besoin est assez simple, et que beaucoup de projets sur le net font du marshalling JSON, je décide d’aller voir ailleurs si l’horizon est plus dégagé. Il faudra que la solution soit au moins iso-fonctionnelle avec Sojo pour que les projets existants puissent migrer sans mal. Ce qui ajoute un dernier besoin, le marshalling de beans en JSON.

JSON.org

Le site json.org fournit une API pour lire et écrire des objets JSONObject, ainsi qu’un parser bas niveau, le JSONTokener. La bonne nouvelle est que l’API supporte aussi bien les String que les flux. Très simple et sans dépendance, la librairie couvre mes besoins initiaux. Mais il n’est pas possible de travailler directement avec des beans Java. Il ne faut pas non plus compter sur la conversion de date. Bref, cet API ne conviendra pas.

FlexJSON

Pour les besoins de l’article Introduction à JSON, j’avais utilisé FlexJSON. Cette API supporte bien le marshalling. Le JSONDeserializer permet de construire les beans à partir d’une chaîne JSON, tandis que le JSONSerializer va s’intéresser à la sérialisation. La configuration se fait à la mode builder en chaînant les appels aux méthodes use(). Pour les dates, il suffit d’utiliser un DateTransformer associé à la classe Date deser.use(Date.class, new DateTransformer("dd/MM/yyyy hh:mm:ss.S"). Sur le JSONSerializer, je constate à regret qu’il n’est pas possible de gérer le formatage des dates de façon globale. Il faut associer le DateTransformer aux propriétés de type Date par leur nom en appelant par exemple : serializer.transform("birthday", new DateTransformer ("dd/MM/yyyy hh:mm:ss.S")).serialize (monBean);. Du point de vue du remplacement de Sojo, tout va bien. Malheureusement, un coup d’œil à la JavaDoc me révèle vite la triste vérité : « Passage obligé par une String ! »
Vous l’aurez compris, encore une fois, je passe mon chemin …

Pour éviter de perdre trop de temps à faire le tour complet de la galaxie des API JSON, je limite mes recherches aux seuls projets ayant des références que je connais.

Jettison, CXF et XStream

J’identifie rapidement Jettison, utilisé par CXF et XStream. Il s’agit en réalité d’une implémentation de la JSR-173 pour JSON. StAX, ou Streaming API for XML, fournit une interface pour lire et écrire des arbres XML sur un flux. Il y a un Reader et un Writer connaissant les éléments de structure des arbres XML. C’est à nous ensuite d’assurer le mapping à la main.
Jettison est beaucoup trop bas niveau pour mon besoin. La seule fonction en commun avec Sojo est la conversion de types. Il n’est pas non plus question d’utiliser CXF seulement pour sérialiser des beans en JSON. Et XStream dans tout ça ? Avec XStream, tout est entièrement orienté vers le XML. Pour convertir un objet en JSON, il faut par exemple utiliser la méthode xstream.toXML(writer, monBean);. La solution est sympathique si vous utilisez déjà XStream et que vous désirez très rapidement sérialiser en JSON. Dans mon cas, d’autres solutions sont en place pour gérer le XML, et je ne souhaite pas en intégrer une nouvelle. Bref, XStream ne fait pas non plus l’affaire.

Jackson

Ayant aussi entendu parler de Jackson, notamment utilisé par Jersey et RestEasy, je me décide à l’étudier de plus près. La première impression est bonne, le site est actif, la performance est mise en avant, et tous les besoins sont couverts (Flux, mapping de bean, et mapping DOM). Le point central de l’API est l’ObjectMapper qui fournit les méthodes readValue et writeValue pour lire et écrire un objet par sa classe depuis un flux ou une chaîne JSON. Le mapper permet de charger les objets JSON dans des collections génériques (Map, List), dans un modèle DOM (JsonNode), ou directement dans un bean. Jackson fournit aussi une interface bas niveau inspirée de StAX pour l’écriture et la lecture de flux JSON. La conversion des dates se fait par configuration sur l’ObjectMapper ou par annotation. Comme dans FlexJson, je peux créer et ajouter mes propres JsonSerializer pour gérer la conversion de certains types spécifiques. L’API est riche, en prime elle implémente JAX-RS et Bean validation.
A l’usage, l’API est simple, bien documentée, et elle répond bel et bien à tous mes besoins. Le voyage pourrait s’arrêter là, mais je souhaite faire le meilleur choix possible et je n’ai pas encore testé google-gson.

Gson

Le but de Gson est de fournir une grande simplicité de développement. Un GsonBuilder permet de configurer et créer une instance de Gson. La classe Gson expose ensuite les méthodes fromJson() et toJson(), c’est limpide. Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy hh:mm:ss.S zz").create(); gson.toJson (monBean, writer);. Gson assure le mapping de bean complet, d’arbre JsonObject, et de Map ou de List. Il y a aussi des annotations, la possibilité de définir ses propres Serializer et Deserializer de type, et l’exclusion des propriétés. A l’exception de JAX-RS et de Bean validation, l’API est iso-fonctionnelle avec Jackson. Nous avons donc une seconde librairie qui répond à tous les besoins. Alors comment choisir ?

Le test de performance

Puisque j’ai besoin de minimiser le temps de traitement des beans, je décide de monter un petit test de performance. Le test consiste à sérialiser et désérialiser 10000 fois un bean simple sur un flux à l’aide d’un objet implémentant l’interface JsonProcessor. Pour l’occasion, j’ai implémenté le JsonProcessor avec chacune des librairies dont j’ai parlé dans l’article.

Ce test a été réalisé sans parallélisation sur mon iMac Intel Core Duo. Mais comme vous pouvez le constater en dehors du mapping manuel avec JSON.org, Jackson met 3 à 5 fois moins de temps que les autres librairies. Sojo est définitivement hors course et XStream s’avère étonnamment lent. Attention, les différentes implémentations sont utilisées sans optimisation d’aucune sorte. Pour approfondir l’analyse, j’ai ajouté le calcul des temps de sérialisation et de désérialisation minimum, maximum et moyen en nano-secondes, dont voici le résultat:

Là encore, Jackson mène la course. En sérialisation, FlexJson affiche des performances à peu de chose près équivalentes à Jackson, mais la désérialisation est très coûteuse.
Étonnamment, c’est Jackson qui atteint le temps maximum. Mais il faut savoir que Jackson maintient un cache des types visités par introspection, ce qui lui évite de refaire cette opération pour le même type. C’est pourquoi la première sérialisation et la première désérialisation prennent plus de temps. Il faudra donc faire attention à ce premier chargement, surtout si vous comptez utiliser beaucoup de types différents. Malgré ce léger défaut, Jackson est définitivement la solution qui répond à mes besoins, et qui remplacera avantageusement Sojo, la solution précédemment en place. A fonctionnalité égale, Jackson est cinq fois plus rapide que Sojo et trois fois plus rapide que Gson. Si vous avez besoin d’une API JSON capable et performante, ou que vous rencontrez des problèmes de performance avec une autre librairie JSON, Jackson est sans aucun doute la solution qu’il vous faut.

Les sources du test sont disponibles sur le google-code de xebia-france dans le dépôt svn/trunk/jsonbench. Pour aller plus loin dans l’analyse ou comparer d’autres librairies de sérialisation, notez que le projet jvm-serializers développe un outil de test de performance pour les librairies de sérialisation Java. L’outil fournit de nombreuses statistiques et supporte déjà un grand nombre d’API.

Références:

Billets sur le même thème :

8 commentaires

  • Very useful article. Did you consider json-lib for your analysis also ? Just curious.

    Thanks
    Romin

  • @Romin Irani: Thanks for youre reading, in fact, I must admit that I did not test json-lib. But I know that it uses json.org API as its core JSON parser and the json.org parser is tested using a manual binding.
    As you can see the manual binding using this API is still less efficient than Jackson.
    But you can very easily implement the test on your own using the shared sources on the xebia-france google-code project.

  • Bonjour,

    Nous utilisons Qooxdoo comme frontend RIA et Java comme backend.
    Nous avons donc besoin de serialisation/deserialisation JSON/Java.

    Nous avons fini par développer un module après avoir utilisé celui fourni par Qooxdoo qui nous est apparu insuffisant à la longue.

    Nous avons mis notre module en open source.
    http://qooxdoo.org/contrib/project/RpcJavaPojo

  • Ah oui, j’ai oublié : notre module est basé sur json.org.

  • Merci pour ce tour d’horizon plutôt complet !

    J’ai déjà eu à faire ce type de choix dans le cadre de plusieurs projets, et je rejoins les conclusions de cet article.

    Pour de la sérialisation JSON, côté serveur (exposition de services REST), Gson est très intéressant notamment pour sa simplicité de mise en œuvre, et sa documentation très détaillée, et très bien organisée.

    Par contre, pour de la désérialisation sur une plateforme Android, j’ai du utiliser Jackson plutôt que Gson. Ce dernier créé énormément d’objets temporaires (provider, factory, etc), et la désérialisation de grands arbres sous Android faisait passer l’essentiel du temps dans le garbage collector..

    J’en parlais ici : http://blog.excilys.com/2010/02/25/android-pour-lentreprise-6-oubliez-gson-jackson-rocks-my-world/

    Seul bémol de Jackson : prise en main moins facile, la doc est moins bien organisée. J’ai souvent découvert les pages donnant les solutions à mes problèmes APRÈS les avoir résolus.. faute d’un bon index.

    Un bon point pour Jackson ceci dit : le code source est bien plus facile à comprendre que celui de Gson.. (quand la doc pêche, faut plonger dans les sources). Du moins, c’est mon ressenti.

Laisser un commentaire