lundi 13 février 2012

xml vs json vs yaml (Java oriented)

Il existe aujourd'hui un ensemble de format permettant de stocker des données. Il y a du choix!

On connait historiquement le format XML mais d'autres formats sont de plus en plus utilisés tels que JSON ou YAML.

Je vous propose ici de faire une petit comparaison de ces 3 formats notamment lorsqu'on doit faire de la sérialisation/déserialisation en Java.

XML : "le classique"

Le bon "vieux" format XML est un standard incontournable de stockage de données.

Ses forces :
  • Il est clairement fortement répandu, voir incontournable.
  • Il possède des langages de description permettant de contractualiser le contenu : DTD (Document Type Definition) ou XML Schema.
  • Il est très outillé.
  • Il est lisible.
Ses faiblesses :
  • Il est très (trop) verbeux.
  • "complexe" à mettre en oeuvre.
Comment on fait en java? 
Il existe plusieurs type d'APIs pour "discuter XML" en Java. Dans une première approche, Il existe JDOM  qui propose 2 orientations. La première est orienté "DOM" qui est de créer une représentation des données (non typé, à base de classe JDOM) XML en JAVA en mémoire. la seconde est orienté "SAX" qui est une APIs évènementielle qui parcourt le document et sur lequel on vient se câbler pour récupérer des morceaux de document.

Du coté des APIs plus haut niveau, JIBX ou JAXB (notamment dans sa version 2) offre un environnement de travail complet sérialiser/déserialiser. Elles permettent notamment de manipuler en java des objets typés.

Si on prend l’exemple de JAXB, l'idée est :
  • de récupérer ou créer le XML Schema représentant le document que l'on souhaite sérialiser/déserialiser (c'est le "contrat")
  • de générer avec XJC (outil de génération de classes de JAXB) les classes (annotées) correspondants à la structure du document. Un plugin maven permet d'intégrer ça au process de build : jaxb2-maven-plugin
  • à l’exécution, utiliser les APIs JAXB avec les classes générés pour écrire ou lire du XML.
Voilà ce que cela peut donner : 
Pour déserialiser : 
JAXBContext jc = JAXBContext.newInstance(User.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(this.getClass().getResource("/user.xsd"));
unmarshaller.setSchema(schema);
User user = (User) unmarshaller.unmarshal(new File("user.xml"));

Pour sérialiser :
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, 
  Boolean.TRUE);
marshaller.marshal( user, new File("user-modified.xml") );

C'est un peu verbeux mais ça marche bien :-)

JSON : "à la mode"

Ses forces :
  • Il est très à la mode et très répandu.
  • Il est bien outillé.
  • Il est peu verbeux.
Ses faiblesses :
  • Il est peu lisible.
  • Il n'existe pas de format de définition de schéma (seulement un draft).
Je trouve le JSON moins lisible pour un humain mais il est souvent utilisé comme format d'échange inter applicatif (entre un client et un serveur par exemple) où le but n'est pas qu'il soit lisible mais qu'il soit léger et concis. C'est un format qui est notamment beaucoup utilisé dans les architectures REST.

Comment je fais en java?
Il existe pas mal de librairies permettant de faire la sérialisation/désérialisation en JAVA. Pour en citer quelques unes : json-lib, google-gson, FlexJSON ou encore Jackson. C'est cette dernière que j'utilise en général. 

Dans le cas de Jackson par exemple, l'APIs pour sérialiser/désérialiser en Java est très simple, il n'y a aucun impact sur votre code. Vous pouvez utiliser vos classes existantes et les sérialiser.

Pour désérialiser :
ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
User user = mapper.readValue(new File("user.json"), User.class);

Pour sérialiser :
mapper.writeValue(new File("user-modified.json"), user);

C'est tout...

YAML : "l'anti balise"

Sa force :
  • Il est très peu verbeux.
  • Il est très lisible.
Ces faiblesses :
  • Il est moins connu et moins répandu.
Le YAML regroupe 2 points très intéressants : il est très lisible et peu verbeux. Comme le précise le site de YAML, son objectif est d'être "human friendly".

Comment je fais en java?
Il y a quelques librairies en java permettant de faire le job : JYaml et SnakeYAML. C'est d'ailleurs cette dernière que j'utilise.

Dans le cas de SnakeYAML, comme pour Jackson avec JSON, il n'y a aucun impact en terme de code et vous pouvez réutiliser vos classes existantes en l'état.

Pour désérialiser :
Yaml yaml = new Yaml();
User user = yaml.loadAs(new File("user.yaml"), User.class);

Pour sérialiser:
Yaml yaml = new Yaml();
yaml.dump(user,new FileWriter("user-modified.yaml"));

C'est tout aussi...

Comparons : 

Bon, et maintenant si on faisait un petit test en exprimant la même chose dans les 3 formats et en regardant le résultat. Je vais baser l'exemple en exprimant un dataSet de CassandraUnit. On peut exprimer un dataSet dans ces 3 formats.

Voici donc une première version xml :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<keyspace xmlns="http://xml.dataset.cassandraunit.org">
    <name>otherKeyspaceName</name>
    <strategy>org.apache.cassandra.locator.SimpleStrategy</strategy>
    <replicationFactor>1</replicationFactor>
    <columnFamilies>
        <columnFamily>
            <name>beautifulColumnFamilyName</name>
            <type>STANDARD</type>
            <keyType>UTF8Type</keyType>
            <comparatorType>LongType</comparatorType>
            <row>
                <key>key10</key>
                <column>
                    <name>11</name>
                    <value>utf8(value11)</value>
                </column>
                <column>
                    <name>12</name>
                    <value>utf8(value12)</value>
                </column>
            </row>
            <row>
                <key>key20</key>
                <column>
                    <name>21</name>
                    <value>uuid(13816710-1dd2-11b2-879a-782bcb80ff6a)</value>
                </column>
            </row>
        </columnFamily>
        <columnFamily>
            <name>columnFamilyWithCompositeType</name>
            <type>STANDARD</type>
            <keyType>CompositeType(LongType,UTF8Type)</keyType>
            <comparatorType>CompositeType(UTF8Type,IntegerType,UTF8Type)</comparatorType>
            <defaultColumnValueType>UTF8Type</defaultColumnValueType>
            <row>
                <key>10:azerty</key>
                <column>
                    <name>aa:10:aa</name>
                    <value>Chuck</value>
                </column>
                <column>
                    <name>aa:10:ab</name>
                    <value>Larry</value>
                </column>
            </row>
        </columnFamily>
    </columnFamilies>
</keyspace>


Voici le même dataSet exprimé en JSON :
{
    "name" : "otherKeyspaceName",
    "replicationFactor" : 1,
    "strategy" : "org.apache.cassandra.locator.SimpleStrategy",
    "columnFamilies" : [{
        "name" : "beautifulColumnFamilyName",
        "type" : "STANDARD",
        "keyType" : "UTF8Type",
        "comparatorType" : "LongType",
        "rows" : [{
            "key" : "key10",
            "columns" : [{
                "name" : "11",
                "value" : "utf8(value11)"
            },
            {
                "name" : "12",
                "value" : "utf8(value12)"
            }]
        },
        {
            "key" : "key20",
            "columns" : [{
                "name" : "21",
                "value" : "utf8(value11)"
            },
            {
                "name" : "12",
                "value" : "uuid(13816710-1dd2-11b2-879a-782bcb80ff6a)"
            }]
        }]
    },
    {
        "name" : "columnFamilyWithCompositeType",
        "type" : "STANDARD",
        "keyType" : "CompositeType(LongType,UTF8Type)",
        "comparatorType" : "CompositeType(UTF8Type,IntegerType,UTF8Type)",
        "defaultColumnValueType" : "UTF8Type",
        "rows" : [{
            "key" : "10:azerty",
            "columns" : [{
                "name" : "aa:10:aa",
                "value" : "Chuck"
            },
            {
                "name" : "aa:10:ab",
                "value" : "Larry"
            }]
        }]
    }]
}

Enfin, toujours le même dataSet exprimé en YAML :
name: otherKeyspaceName
replicationFactor: 1
strategy: org.apache.cassandra.locator.SimpleStrategy
columnFamilies:
- name: beautifulColumnFamilyName
  type: STANDARD
  keyType: UTF8Type
  comparatorType: LongType
  rows:
  - key: key10
    columns:
    - {name: 11, value: utf8(value11)}
    - {name: 12, value: utf8(value12)}
  - key: key20
    columns:
    - {name: 21, value: utf8(value11)}
    - {name: 12, value: uuid(13816710-1dd2-11b2-879a-782bcb80ff6a)}
- name: columnFamilyWithCompositeType
  type: STANDARD
  keyType: CompositeType(LongType,UTF8Type)
  comparatorType: CompositeType(UTF8Type,IntegerType,UTF8Type)
  defaultColumnValueType: UTF8Type 
  rows:
  - key: "10:azerty"
    columns:
    - {name: "aa:10:aa", value: Chuck}
    - {name: "aa:10:ab", value: Larry}

et là on se dit, tiens, c'est sympa le YAML :-).

Pour exprimer la même chose, il aura fallu :

  • 1131 octets au format XML (non formaté)
  • 793 octets au format JSON (non formaté)
  • 779 octets au format YAML

Conclusion
L'objet de cet article n'est pas de dire qui est le meilleur. Le cas de stockage de DataSet est en effet particulier car un des critères est justement qu'il soit "human readable" mais ce qui est surprenant c'est qu'au final, c'est YAML qui est le plus lisible mais aussi le plus concis.

En général, chacun de ces formats est utilisés dans des contextes différents même s'il est vrai que le XML et le JSON, qui sont plus répandus, sont souvent opposés en terme de comparaison : 
web services SOAP/XML vs web services REST/JSON. 
Oui le XML est plus verbeux que le JSON mais il est plus "contractuel". 

On retrouve en revanche plus le format YAML comme format de fichiers de configuration comme c'est le cas pour le fichier de configuration de Cassandra par exemple. Je trouve en tous cas ce format de fichier comme une alternative intéressante qu'il ne faut pas oublier!

7 commentaires:

insitu a dit…

Comparaison intéressante Jérémy !

Toutefois, je crois que tu as fais une légère confusion: JDOM n'est pas une API pour manipuler du XML mais un outil Java (http://www.jdom.org/), c'est DOM qui est l'API standardisée par le W3C (http://www.w3.org/DOM/DOMTR) et implémentée dans le JDK depuis ... longtemps.

Thomas a dit…

Merci, je regarderai Yaml d'un autre oeil.

Dans les caractéristiques de JSON : un gros plus (sa seule raison d'être ?) : c'est du JavaScript et donc nativement interprété par les navigateurs. Idéal donc pour les services Rest/Ajax.

Thomas a dit…

Merci, je regarderai Yaml d'un autre oeil.

Dans les caractéristiques de JSON : un gros plus (sa seule raison d'être ?) : c'est du JavaScript et donc nativement interprété par les navigateurs. Idéal donc pour les services Rest/Ajax.

Jérémy Sevellec a dit…

@insitu, Je crois que nous sommes d'accord. C'est une question de sens. JDOM est malgré tout une API Java qui te permet de manipuler du XML (oui dans le JDK), plus simplement qu'avec DOM... non?

Jérémy Sevellec a dit…

@insitu article édité pour que ça soit plus clair. Merci pour ton retour!

Arnaud a dit…

Il existe aussi http://msgpack.org/
Mais c'est pas human friendly.

Chris a dit…

Peut-être parce que ce qui est simple est concis.

bon article bien référencé sur google.

A+