Nous sommes de plus en plus souvent amenés à échanger des données en JSON en remplacement de XML.
JSON est né avec AJAX et les Web Services REST dans le but d’obtenir un format d’échange simple et peu verbeux (à l’inverse du XML).
Pour plus de détails sur JSON voir l’article de Wikipedia ou pour les plus techniques le site json.org.
Mais Java ne permettait pas, jusqu’à peu, de traiter nativement des messages JSON.
La version 7.0 de Java EE intègre le support de la JSR 353 qui est l’API Java pour le JSON Processing (JSON-P) et définit l’API pour analyser, générer, transformer et requêter du JSON.
L’avantage de cette spécification est de standardiser le traitement des flux JSON en Java. Mais nous allons voir dans cet article si elle apporte suffisamment en comparaison de frameworks Java de traitement JSON tels que Gson, même pour des traitements très simples.
Nous créons un objet People qui nous permet de tester les différents cas (tableau, composition, …) :
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
public class People {
private String name;
private String firstName;
private Date birthDate;
private Address address;
private List<PhoneNumber> phones;
...
public class Address {
private String addressStreet1;
private String addressStreet2;
private int addressNumber;
private String zipcode;
private String town;
...
public class PhoneNumber {
private String type;
private String number;
...
[/pastacode]
Création d’un jeu de tests :
[pastacode lang=”java” message=”Données de test” highlight=”” provider=”manual”]
people = new People();
Address address = new Address();
address.setAddressNumber(12);
...
PhoneNumber mobilePhone = new PhoneNumber();
...
PhoneNumber workPhone = new PhoneNumber();
...
people.setFirstName("Paul");
people.setName("Dubois");
...
ArrayList<PhoneNumber> phones = new ArrayList<PhoneNumber>();
phones.add(mobilePhone);
phones.add(workPhone);
people.setPhones(phones);
[/pastacode]
JSON-P
JSON-P propose deux modèles de développement :
- Le modèle objet crée un arbre qui représente les données JSON en mémoire (équivalent au DOM en XML).
- Le modèle Streaming permet de parcourir un document JSON à partir d’évènements (début ou fin d’un objet ou d’un tableau, lecture d’une clé ou d’une valeur, …) (équivalent StAX en XML).
Les classes principale de l’API sont :
- JsonObjectBuilder & JsonArrayBuiler : pour créer des objets ou tableaux JSON
- JsonReader & JsonWriter : pour lire et écrire des objets JSON
- JsonObject, … : réprésentation des types de données JSON
[pastacode lang=”java” message=”JSON-P” highlight=”1,2,5,13,22,25,29″ provider=”manual”]
JsonObjectBuilder jsonBuilder = Json.createObjectBuilder()
.add("firstName", people.getFirstName())
.add("name", people.getName())
.add("birthDate", people.getBirthDate().toString())
.add("address", Json.createObjectBuilder()
.add("addressNumber", people.getAddress().getAddressNumber())
.add("addressStreet1", people.getAddress().getAddressStreet1())
.add("addressStreet2", people.getAddress().getAddressStreet2())
.add("addressZipCode", people.getAddress().getZipcode())
.add("town", people.getAddress().getTown())
.build()
);
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (PhoneNumber phoneNumber : people.getPhones()) {
arrayBuilder.add(Json.createObjectBuilder()
.add("type", phoneNumber.getType())
.add("number", phoneNumber.getNumber()));
}
jsonBuilder.add("phones", arrayBuilder);
JsonObject jsonObject = jsonBuilder.build();
StringWriter stWriter = new StringWriter();
JsonWriter jsonWriter = Json.createWriter(stWriter);
jsonWriter.writeObject(jsonObject);
jsonWriter.close();
String jsonData = stWriter.toString();
[/pastacode]
1 & 2 – Création du JsonObjectBuilder et ajout des propriétés simples.
5 – Ajout d’un objet Address à l’objet People
13 – Création d’un tableau pour représenter la liste de téléphone
22 – Création du JsonObject représentant notre People
25 & 29 – utilisation d’un JsonWriter pour représenter notre People sous forme de String
[pastacode lang=”javascript” message=”Résultat JSON-P” highlight=”” provider=”manual”]
{
"firstName": "Paul",
"name": "Dubois",
"birthDate": "Mon Jun 12 08:06:24 CEST 1978",
"address": {
"addressNumber": 12,
"addressStreet1": "Rue de la République",
"addressStreet2": "Immeuble des tests",
"addressZipCode": "13001",
"town": "Marseille"
},
"phones": [
{
"type": "mobile",
"number": "0612345678"
},
{
"type": "work",
"number": "0412345678"
}
]
}
[/pastacode]
GsON
Gson est une librairie Java développée par Google pour transformer des objets Java vers leur représentation JSON et vice-versa.
Les objectifs principaux de Gson sont :
- Fournir un mécanisme simple pour convertir un objet Java en JSON et vice-versa
- Permettre de convertir des objets existants et non modifiables
- Permettre d’obtenir une représentation personnalisée des objets
La classe principale de Gson est Gson. Elle permet de convertir très rapidement un objet Java en JSON :
[pastacode lang=”ruby” message=”Gson” highlight=”” provider=”manual”]
Gson gson = new Gson();
gson.toJson(people);
[/pastacode]
Le code parle de lui-même…
[pastacode lang=”javascript” message=”Résultat Gson” highlight=”” provider=”manual”]
{
"name": "Dubois",
"firstName": "Paul",
"birthDate": "Jun 12, 1978 8:06:24 AM",
"address": {
"addressStreet1": "Rue de la République",
"addressStreet2": "Immeuble des tests",
"addressNumber": 12,
"zipcode": "13001",
"town": "Marseille"
},
"phones": [
{
"type": "mobile",
"number": "0612345678"
},
{
"type": "work",
"number": "0412345678"
}
]
}
[/pastacode]
Le résultat est sensiblement le même que pour JSON-P si ce n’est le formatage des dates différent.
Gson propose un mécanisme de Serializer et Deserializer personnalisé qui nous permet d’appliquer un formatage particulier à un type donné (Date par exemple) ou plus généralement une représentation particulière pour un objet.
L’exemple suivant montre la création d’un Serializer personnalisé :
[pastacode lang=”java” message=”” highlight=”1,2,4″ provider=”manual”]
public class PeopleSerialiser implements JsonSerializer<People> {
public JsonElement serialize(People people, Type arg1,
JsonSerializationContext arg2) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("firstName", people.getFirstName());
jsonObject.addProperty("name", people.getName());
jsonObject.addProperty("birthDate", people.getBirthDate().toString());
jsonObject.addProperty("addressNumber", people.getAddress().getAddressNumber());
jsonObject.addProperty("addressStreet1", people.getAddress().getAddressStreet1());
jsonObject.addProperty("addressStreet2", people.getAddress().getAddressStreet2());
jsonObject.addProperty("addressZipCode", people.getAddress().getZipcode());
jsonObject.addProperty("town", people.getAddress().getTown());
final JsonArray jsonAuthorsArray = new JsonArray();
for (final PhoneNumber phoneNumber : people.getPhones()) {
final JsonObject number = new JsonObject();
number.addProperty("type", phoneNumber.getType());
number.addProperty("number", phoneNumber.getNumber());
jsonAuthorsArray.add(number);
}
jsonObject.add("phones", jsonAuthorsArray);
return jsonObject;
}
}
[/pastacode]
1 & 2 : Notre Serializer implémente l’interface JsonSerializer et définit la méthode serialize qui prend notamment en paramètre un objet People pour renvoyer un JsonElement.
4 : La suite ressemble un peu à JSON-P avec la création d’un JsonObject qui est la représentation de l’objet en JSON (JsonArray pour les tableaux).
Dans cet exemple on construit une représentation de notre objet People en publiant une adresse “à plat” et non comme objet au sein de People.
[pastacode lang=”java” message=”” highlight=”” provider=”manual”]
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(People.class, new PeopleSerialiser());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
gson.toJson(people);
[/pastacode]
Afin d’utiliser notre serializer on crée un GsonBuilder (1) auquel on passe notre serializer (2).
L’option setPrettyPrinting permet d’obtenir une représentation JSON formatée et indentée.
Puis cette fois on crée notre objet Gson grâce à la méthode create du GsonBuilder (4).
On obtient un résultat conforme à nos attentes :
[pastacode lang=”javascript” message=”Résultat Gson serializer” highlight=”” provider=”manual”]
{
"firstName": "Paul",
"name": "Dubois",
"birthDate": "Mon Jun 12 08:06:24 CEST 1978",
"addressNumber": 12,
"addressStreet1": "Rue de la République",
"addressStreet2": "Immeuble des tests",
"addressZipCode": "13001",
"town": "Marseille",
"phones": [
{
"type": "mobile",
"number": "0612345678"
},
{
"type": "work",
"number": "0412345678"
}
]
}
[/pastacode]
Conclusion
L’api JSON-P au sein de JEE7 est une vrai avancée car elle permet de traiter nativement du JSON. Mais le manque principal actuel est le binding natif d’un objet Java vers JSON.
On voit que dans le cas où on construit une API basée sur des échanges JSON et des objets métiers, l’utilisation de JSON-P sera bien trop verbeuse et on privilégiera l’utilisation de Gson (ou d’autres framework comme celui-ci) dont le coût de construction sera quasiment nul.
Dans le cas où l’on doit intégrer des messages JSON existants, la question se pose. Soit nos objets métiers n’existent pas et on va avoir tendance à utiliser Gson et le binding, soit nos objets métiers existent et la question se pose car la verbosité des deux API sera quasiment identique.
Il faudrait ensuite approfondir l’étude des deux sur des aspects tels que les performances pour faire son choix. Gson donne quelques indications à ce sujet (ici) mais un comparatif sur un même jeu de données serait intéressant.
Ce manque de binding a clairement été identifié par Oracle, notamment lors d’un sondage sur les nouveautés à apporter à JEE8. Une nouvelle JSR va être soumise d’ici peu pour intégrer une API Java JSON Binding à JEE8.
Prochains tests lors de la sortie de JEE8…