Introduction
Sérialisation ?
- Données en mémoire (tas) sous la forme d'un graphe d'objets
- Sérialisation : transformation d'un graphe d'objets en séquence d'octets ou de caractères
-
Pourquoi ?
- Pour sauvegarder les objets sur une mémoire de masse (et pouvoir les restaurer plus tard)
- Pour échanger des objets avec d'autres processus (localement ou à distance via le réseau)
- Pour l'édition humaine de données à utiliser : fichier de configuration
Familles de formats de sérialisation
-
Formats binaires
- Représentation par une séquence de bits
- Avantages : formats compacts, compression possible des redondances, possibilités d'indexation pour accès aléatoire
- Inconvénient : non lisibilité par un humain (donc débuggage plus difficile)
-
Formats textuels
- Représentation par une séquence de caractères
- Avantage : human-friendly, meilleure compatibilité avec des vieux protocoles de communication textuels (SMTP...)
- Inconvénient : représentations lourdes, peu adaptées à des données binaires par nature (sons, images...), surcoût de calcul
Quelques formats usuels
- Format de sérialisation binaire Java : utilisé par l'API standard pour sauvegarder des objets
- Format XML (eXtended Markup Language) : <description>format textuel à balises</description> ; format dérivé binaire : Binary XML
- Format JSON (JavaScript Object Notation) : dérivé de la façon de représenter des objets en JavaScript (comme son nom l'indique), très populaire pour faire des requêtes asynchrones sur le web ; supporte les dictionnaires, tableaux, chaînes et nombres ; format dérivé binaire : BSON
- Format YAML (YAML ain't Markup Language) : format lisible utilisant des dictionnaires, listes avec la possibilité d'inclure des références et des indications de type (donc nativement plus adapté pour représenter un graphe d'objets)
- Format CSV (Comma Separated Values) : format textuel adapté pour la sérialisation de matrices (pas d'objets), chaque colonne est séparée par un séparateur (typiquement virgule)
- Format INI : format textuel utilisé pour les fichiers de configuration (dictionnaire clé=valeur) ; utilisé par les fichiers properties en Java
- ...
Formats dédiés
- Spécialisation possible de formats généraux (comme XML) en utilisant un schéma
- Le schéma ajoute des contraintes pour les balises utilisables ; différents types de schémas en XML : DTD, XSD, RelaxNG, Schematron...
- Utile pour l'échange de données entre applications hétérogènes pour vérifier la cohérence des données
- Quelques languages XML issus de schémas : XHTML, MathML, OpenDocument, SVG...
Approches d'API de lecture/écriture
-
Manipulation d'arbres/graphes d'objets
- Approche aisée : accès direct et aléatoire aux structures en mémoire
- Approche coûteuse : les structures sont intégralement en mémoire
-
Manipulation d'événements
- On ne considère pas la structure mais des événements élémentaires : ouverture d'un objet, d'un tableau, écriture d'une valeur, fermeture d'un objet...
- Les structures ne résident pas en mémoire, les accès sont séquentiels
-
Styles d'API en lecture :
- Pushing par rappel de fonctions callback
- Pulling par itérateur d'événements
Sérialisation Java
Sérialisation automatique
Caractéristiques de la sérialisation Java
- Simple d'usage : tout est réalisé automagiquement par introspection
- Pour sérialiser un graphe d'objets : il suffit que tous les objets de ce graphe implantent l'interface marqueur Serializable
- La plupart des objets (tableaux, listes, dictionnaires...) de l'API standard sont déjà Serializable
- Tous les champs d'un objet sont sérialisés (même private) saufs ceux qualifiés de transient
-
La version de la classe utilisée lors de la sérialisation doit être la même que celle utilisée lors de la désérialisation :
- private static final long serialVersionUID = 4242L; // A définir explicitement... sinon le compilateur le génère automatiquement
-
Utilisation de la sérialisation standard Java à éviter pour désérialiser du contenu fourni par un utilisateur externe : possibilité pour un attaquant d'insérer des objets pouvant potentiellement exécuter du code ou créer des denis de service
- Danger limitable en utilisant des filtres de désérialisation (introduits par la PEP 415 et implantés à partir de Java 17)
Sérialisons un graphe d'objets
package fr.upem.jacosa.serialization;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class BookSerialization
{
public static class Book implements Serializable
{
private final String title;
private final int price;
public Book(String title, int price)
{
this.title = title;
this.price = price;
}
}
public static void main(String[] args) throws IOException
{
String file = args[0];
List<Book> l = new ArrayList<Book>();
l.add(new Book("Hamlet", 10));
Book b = new Book("Othello", 5);
l.add(b);
l.add(b);
try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get(file))))
{
oos.writeObject(l);
}
}
}
Graphe d'objet sérialisé
Résultat de la sérialisation
Rechargeons les livres sérialisés
List<Book> l = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serializedBooks")))
{
l = (List<Book>)ois.readObject();
}
Exceptions potentielles
- FileNotFoundException si le fichier n'est plus là
- ClassNotFoundException si la classe n'est pas présente
- InvalidClassException* s'il y a discordance entre les versions de classe
- ObjectStreamException* si le format du fichier est altéré
-
Possibilité de vérifier un objet désérialisé en implantant ObjectInputValidation :
- la méthode validateObject() vérifie l'objet et lève InvalidObjectException* en cas de problème
Les exceptions * héritent de IOException
Sérialisation avancée
Plus de contrôle avec Externalizable
-
Plutôt que d'utiliser la sérialisation automatique par introspection, on fait tout manuellement en écrivant/lisant les champs :
- Méthode writeExternal(ObjectOutput out) pour écrire des données : out.writeInt(i), out.writeUTF(s), out.writeObject(o)
- Méthode readExternal(ObjectInput in) pour lire les données sérialisées : in.readInt(), in.readBoolean(), in.readUTF(), in.readObject()...
- Désérialisation: la classe doit posséder un constructeur sans argument pour créer l'instance ; ensuite writeExternal sera appelé.
Sérialisons un livre en compressant son titre
package fr.upem.jacosa.serialization;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Book implements Externalizable
{
private String title;
private int price;
public Book() { } // A no-arg constructor is required for Externalizable
public Book(String title, int price) { /* ... */ }
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeShort(price); // we assume that the price does not reach 2^15 euros
// we compress the title by storing first its length
// and each sequence of identical characters prefixed by their repeat number
out.writeShort(title.length());
char currentChar = '\0'; int number = 0;
for (int i = 0; i <= title.length(); i++)
{
if (i < title.length() && title.charAt(i) == currentChar) number++;
else {
if (number > 0) { out.writeChar(currentChar); out.writeByte(number); }
if (i < title.length()) { currentChar = title.charAt(i); number = 1; }
}
}
}
public void readExternal(ObjectInput in) throws IOException
{
price = in.readShort();
int titleLength = in.readShort();
StringBuilder sb = new StringBuilder(); int i = 0;
while (sb.length() < titleLength)
{
char c = in.readChar();
byte repeats = in.readByte();
for (byte j = 0; j < repeats; j++) sb.append(c);
}
title = sb.toString();
}
}
Quelques remarques pour approfondir
-
Sérialisation de tous les champs non-transient du graphe
- attention aux données confidentielles en cas de communication externe
-
Modification possible de fichiers sérialisés
- altération des données, déni de service...
- possibilité d'utiliser un SignedObject
- Sérialisation des beans par XML : XMLEncoder, XMLDecoder
Manipulation de données en XML
Le format XML
Un peu de XML
- Évolution de Standard Generalized Markup Language (SGML)
-
Principe : emboîtement de sections délimitées par des balises (<balise> ... </balise>)
- représentation possible par un arbre
- Plusieurs espaces de nom peuvent cohabiter : <namespace1:balise>, <namespace2:balise>
- Les balises peuvent accueillir des attributs : <balise attribut1="valeur1" attribut2="valeur2">...</balise>
- <balise></balise> (balise sans contenu) peut être remplacé par <balise />
- Une balise peut avoir pour enfant un noeud CDATA (chaîne de caractère)
-
Certains caractères sont spéciaux : ", &, ', < et >
- déspécialisables en ", &, ', <, >
- On peut forcer un passage en CDATA en l'entourant ainsi : <![CDATA[ ... ]]> (plus besoin de déspécialiser)
- <!-- Voici un exemple de commentaire -->
Exemple de document XML
<?xml version="1.0"?> <shelf> <book license="PUBLIC_DOMAIN"> <title>Quatrevingt-treize</title> <author>Victor Hugo</author> </book> <!-- Here is a little more complex book --> <book license="COPYRIGHT"> <title><![CDATA[ <title>HTML for Babies</title> ]]></title> <author>Vanden-Heuvel</author> </book> </shelf>
Document Object Model (DOM)
API Document Object Model
- API définie par la W3C avec de nombreuses implantations : actuellement version 3 (v4 en développement)
- Manipule le document XML sous la forme d'un arbre
- Permet d'associer aussi des événements aux noeuds
- Incontournable pour la manipulation de pages HTML avec JavaScript (mais ce n'est pas l'objet de ce cours)
- API présente dans le JDK Java (org.w3c.dom)
- Présentation complète de l'API sur le site de JM Doudoux
Initialisation d'analyseur DOM
InputStream is = ...;
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e)
{
}
Document doc = builder.parse(is);
Les principaux noeuds
Ils implantent l'interface org.w3c.dom.Node
| Noeuds | Enfants possibles |
| Document | Element, ProcessingInstruction, Comment, DocumentType |
| DocumentFragment | |
| Element | Element, Text, Comment, ProcessingInstruction, CDataSection, EntityReference |
| Attr | Text, EntityReference |
| Text | Feuille |
| CDataSection | Feuille |
| Comment | Feuille |
Les autres noeuds
D'autres noeuds potentiellement utiles
| ProcessingInstruction | Feuille |
| Entity | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
| EntityReference | |
| Notation | Feuille |
Informations sur un noeud
- String getNodeName() : nom de l'élément, de l'attribut
- String getNodeValue() : valeur textuelle contenue pour un attribut, du texte, un commentaire (null pour un élément)
- String getTextContent() : contenu textuel du noeud (en explorant le sous-arbre)
- short getNodeType() : constante pour le type de noeud (évite d'utiliser instanceof)
- NodeList getChildNodes() : tous les noeuds enfants
- NodeList getElementsByTagName(String tagName) : noeuds enfants portant un certain nom
- Node getParentNode() : noeud parent
- String getAttribute(String name) : attribut d'un élément
Comment utiliser NodeList et NamedNodeMap ?
En utilisant (entre-autres) les méthodes int getLength() et Node item(int i)
Ne pas hésiter à consulter la Javadoc
Récupérons tous les titres de livres
// parse and fetch the XML document
Document doc = builder.parse(is);
// get the shelf that is the root element
Element shelf = doc.getDocumentElement();
// iterate over each book of the shelf
for (int i = 0; i < shelf.getChildNodes().getLength(); i++)
{
Element book = shelf.getChildNodes().item(i);
String title = book.getElementsByTagName("title")
.item(0).getTextContent();
System.out.println(title);
}
Modification d'un arbre
DOM peut être aussi utilisé pour modifier un arbre :
- Node cloneNode(boolean deepCopy) : pour copier un noeud (ainsi que ses descendants si copie profonde)
- Node removeChild(Node oldChild) : pour supprimer un enfant
- Node replaceChild(Node newChild, Node oldChild) : pour le remplacement de noeud
- void setNodeValue(String nodeValue) : pour indiquer la valeur d'un noeud (CDATA, valeur d'attribut)
- void setAttribute(String key, String value) : pour changer un attribut d'un élément
Là encore, la Javadoc est utile !
Cas particulier des documents HTML
-
HTML ≠ XML
- HTML repose sur le format SGML (normalisé en 1986)
- XML (normalisé en 1999) est une version plus simple de SGML facilitant l'interopérabilité
- Il existe une variante de HTML basée sur le XML : XHTML (la syntaxe est moins permissive que le HTML standard)
-
HTML dans la vraie vie
- Peu de pages sont rédigées en XHTML
- Aussi bien pour le HTML que le XHTML, on rencontre souvent des erreurs de syntaxe
- Les analyseurs syntaxiques utilisés par les navigateurs sont très permissifs
- Alors que le parser DOM intégré dans le JDK sera perturbé par du XHTML avec erreurs (le HTML classique ne pouvant être analysé)
- Solution : utiliser un analyseur plus tolérant pour du web scraping
Exemple : récupération des conditions de circulation sur le site sytadin.fr
package fr.upem.jacosa.serialization;
import org.jsoup.Jsoup;
import java.io.IOException;
import java.util.regex.Pattern;
public class JSoupDemo {
/**
* With this demonstration of JSoup, we webscrap the trafic information in Paris area
* using the website sytadin.fr
* This code may be obsolete if the Sytadin website changes.
*/
public static void main(String[] args) throws IOException {
var jamRegex = Pattern.compile("([0-9]+) km");
var doc = Jsoup.connect("http://www.sytadin.fr/").get();
System.out.println("Title: " + doc.title());
var jam = doc.getElementById("cumul_bouchon");
int jamLength = -1;
for (var child: jam.children()) {
if (child.tagName().equals("img")) {
String alt = child.attr("alt");
var matcher = jamRegex.matcher(alt);
if (matcher.matches())
jamLength = Integer.parseInt(matcher.group(1));
}
if (jamLength >= 0) break;
}
if (jamLength < 0)
System.out.println("Cannot find jam length");
else
System.out.println(String.format("Jam length: %d", jamLength));
}
}
SAX
Simple API for XML (SAX) : org.xml.sax
= Approche évenementielle de type push (événements récupérés par callbacks)
- API présente dans le JDK Java
- Initialisation d'un XMLReader
-
Création d'un ContentHandler définissant les méthodes de callback à utiliser lors de l'analyse et rattachement avec xmlReader.setContentHandler(ch) :
- {start, stop}Document()
- {start, stop}Element(uri, localName, qname, [attributes])
- setDocumentLocator(locator)
- characters(ch, start, length)
- ...
- Arghh ! Il y a trop de méthodes à implanter ; utilisons plutôt DefaultHandler avec une implantation vide de toutes les méthodes
- Lancement de l'analyse avec xmlReader.parse(inputSource)
Référencement des balises d'un fichier XML avec SAX
package fr.upem.jacosa.serialization;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Set;
import java.util.TreeSet;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
public class SAXReferencer
{
public static void main(String[] args) throws IOException, SAXException, ParserConfigurationException {
Reader r = new InputStreamReader(System.in);
// Where to store each tag name
final Set<String> tagSet = new TreeSet<String>();
XMLReader saxReader = SAXParserFactory.newDefaultInstance().newSAXParser().getXMLReader();
saxReader.setContentHandler(new DefaultHandler() {
public void startElement(
String uri, String localName, String qName, Attributes attributes)
{
tagSet.add(qName); // We use the qualified name
}
});
saxReader.parse(new InputSource(r));
System.out.println(tagSet);
}
}
StAX
API StAX
- Approche évenementielle de type pull avec deux styles d'accès : par curseur ou itérateur
- API présente dans le JDK Java
- Avantage de l'approche curseur sur l'approche itérateur : moins d'objets alloués en mémoire
-
Choisir l'approche itérateur ou curseur ?
- En cas de besoin de stocker les événements ⟶ approche itérateur
- Sinon (dans la majorité des cas) ⟶ approche curseur
- Création du parser à partir d'une fabrique : XMLInputFactory factory = XMLInputFactory.newInstance()
- Description complète de l'API sur le site de JM Doudoux
Approche par curseur
- Création du XMLStreamReader à partir de la factory : XMLStreamReader reader = xmlif.createXMLStreamReader(input)
-
Récupération du type d'événement avec reader.next() (constante entière : XMLStreamConstants.START_DOCUMENT, XMLStreamConstants.END_DOCUMENT, XMLStreamConstants.COMMENT, XMLStreamConstants.START_ELEMENT, XMLStreamConstants.END_ELEMENT, XMLStreamConstants.CHARACTERS...)
- ⚠ Toujours tester avant s'il existe un élément suivant avec reader.hasNext() (sinon on arrive à la fin du fichier)
-
Possibilité d'obtenir des informations supplémentaires avec des getters :
- Nom de la balise : String reader.getName()
- Texte rencontré : String reader.getText()
- Informations sur les attributs d'une balise : int reader.getAttributeCount(int i), String reader.getAttributeName(int i), String reader.getAttributeValue(int i)
- ...
- Génération de deux événements START_ELEMENT et END_ELEMENT par les balises auto-fermantes du types <foo /> comme si elles étaient de la forme <foo></foo>
- Filtration possible d'un reader pour obtenir uniquement les éléments d'intérêt avec XMLEventReader filteredReader = xmlif.createFilteredReader(reader, r -> r.isStartElement()); (pour obtenir uniquement les balises ouvrantes)
☞ Une approche similaire par curseur est également utilisée pour parcourir des enregistrements de BDD avec l'API JDBC
Affichage d'événements avec l'approche par curseur
package fr.upem.jacosa.serialization;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.Arrays;
import java.util.HashMap;
/**
* Very simple application to print the encountered tags in a XML document
* using the cursor approach of the StAX API.
*/
public class SimpleStAXCursorApp {
public static void main(String[] args) throws XMLStreamException {
XMLInputFactory xmlif = XMLInputFactory.newInstance();
XMLStreamReader reader = xmlif.createXMLStreamReader(System.in);
try {
while (reader.hasNext()) {
int eventType = reader.next();
switch (eventType) {
case XMLStreamConstants.START_DOCUMENT -> System.out.println("Start of the document");
case XMLStreamConstants.END_DOCUMENT -> System.out.println("End of the document");
case XMLStreamConstants.COMMENT -> System.out.println("Encountered comment: " + reader.getText());
case XMLStreamConstants.START_ELEMENT -> {
var attributes = new HashMap<String, String>();
for (int i = 0; i < reader.getAttributeCount(); i++)
attributes.put(reader.getAttributeName(i).getLocalPart(), reader.getAttributeValue(i));
System.out.println("Start element " + reader.getName() + " with attributes " + attributes);
}
case XMLStreamConstants.END_ELEMENT -> System.out.println("End element " + reader.getName());
case XMLStreamConstants.CHARACTERS -> System.out.println("Encountered characters " + reader.getText());
}
}
} finally {
reader.close();
}
}
}
Recherche de livres d'un auteur donné
- Fichier XML de livres
- Comment obtenir la liste des livres de tous les livres d'un auteur donné ?
- Problème : il s'agit de la collection de la BNF avec 40 millions de livres ; heureusement elle est triée par auteur
- Chargement du fichier XML avec DOM impossible
- On utilise un XMLStreamReader
package fr.upem.jacosa.serialization;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
/** Extract all the bookks from a given author using a cursor with StAX */
public class BookPuller2
{
public static String readText(XMLStreamReader reader) throws XMLStreamException {
int type = reader.next();
assert(type == XMLStreamConstants.CHARACTERS);
return reader.getText().trim();
}
public static void main(String[] args) throws IOException, XMLStreamException {
String expectedAuthor = args[0];
XMLInputFactory xmlif = XMLInputFactory.newInstance();
XMLStreamReader reader = xmlif.createXMLStreamReader(BookPuller2.class.getResourceAsStream("/books/books.xml"));
try {
// Iterate over the events
String currentTitle = null;
String currentAuthor = null;
int eventType = reader.next();
while (eventType != XMLStreamConstants.END_DOCUMENT && (currentAuthor == null || expectedAuthor.compareTo(currentAuthor) <= 0))
{
if (eventType == XMLStreamConstants.START_ELEMENT)
{
if (reader.getName().getLocalPart().equals("book"))
{ currentTitle = currentAuthor = null; }
if (reader.getName().getLocalPart().equals("title"))
currentTitle = readText(reader);
else if (reader.getName().getLocalPart().equals("author"))
currentAuthor = readText(reader);
} else if (eventType == XMLStreamConstants.END_ELEMENT && reader.getName().getLocalPart().equals("book"))
{
if (expectedAuthor.equals(currentAuthor))
System.out.println(currentTitle);
}
eventType = reader.next();
}
} finally {
reader.close();
}
}
}
Manipulation de données en JSON
Présentation
Bibliothèque JSON
JSON est un format simple à analyser, il existe des bibliothèques pour quasiment tous les langages :
- En Java : paquetage org.json ; pas présent dans le JDK mais inclus dans le SDK Android
- En JavaScript : intégré dans le langage avec l'objet JSON : JSON.parse() (ne pas utiliser eval)
- En Python : module json en standard
Toutes les bibliothèques permettent de convertir dictionnaires et tableaux en JSON (et vice-versa) ; le support de la lecture/écriture en streaming est plus rare. \\
Types supportés :
- Dictionnaire : {"clé1": "valeur1", "clé2": "valeur2", ...}
- Tableau : ["a", "b", "c", ...]
- Primitifs : ["chaîne UTF-8", 4241, 3.14, true, false, null]
Site de référence : http://json.org/
Une bibliothèque en JSON
{ "shelf": [
{"title": "Quatrevingt-treize",
"author": "Victor Hugo",
"price": 5,
"license": "PUBLIC_DOMAIN"},
{"title": "<title>HTML for babies</title>",
"author": "Vanden-Heuvel",
"price": 10,
"license": "COPYRIGHT"}
] }
Quelques exemples avec org.json
Arborescence de fichiers en JSON
Ce dont nous avons besoin :
- Classe File pour représenter un chemin de fichier : File f = new File(...);
- f.isFile() et f.isDirectory() pour savoir ce que représente un chemin
- File[] files = f.listFiles() pour lister tous les enfants d'un répertoire
- ArrayList<File> pour stocker les listes de fichier
- JSONArray pour la conversion en tableau JSON
- TreeMap<File> pour stocker les fichiers d'un répertoire en ordre lexicographique
- JSONObject pour la conversion en dictionnaire JSON
Niveau 1 : liste des fichiers enfant d'un répertoire
package fr.upem.jacosa.serialization;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
public class JSONDirectoryListing
{
public static void main(String[] args)
{
File d = new File(args[0]);
List<File> l = new ArrayList<File>();
for (File f: d.listFiles())
l.add(f);
JSONArray array = new JSONArray(l);
System.out.print(array.toString());
}
}
Niveau 2 : arborescence complète d'un répertoire
package fr.upem.jacosa.serialization;
import java.io.File;
import java.util.Map;
import java.util.TreeMap;
import org.json.JSONObject;
public class JSONDirectoryTree
{
/** Recursively build the tree */
public static Object getTree(File root)
{
if (root.isDirectory())
{
Map<String, Object> tree = new TreeMap<String, Object>();
for (File f: root.listFiles())
tree.put(f.getName(), getTree(f));
return tree;
} else
return root.getName();
}
public static void main(String[] args)
{
File root = new File(args[0]);
Object tree = getTree(root);
if (tree instanceof String)
System.err.println(root + " is a simple file");
else // root is a directory
System.out.print(new JSONObject(tree).toString());
}
}
Résultats
Niveau 1 :["music", "pictures", "videos"]Niveau 2 :
{
"music": { "MysteriousMusic.flac": true,
"TheSongOfJava.mp3": true,
"TicTac.wav": true
},
"pictures": { "duke.png": true,
"gosling.jpg": true
},
"videos": { "motion.flv": true,
"video2.avi": true,
"video.mkv": true
}
}
Recherche d'une clé dans un fichier JSON
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.json.JSONObject;
/** Program searching a value linked to a key into a JSON dictionary */
public class JSONKeyFinder
{
public static void find(JSONObject obj, String searchedKey, List<Object> l)
{
for (Iterator<String> keyIt = obj.keys(); keyIt.hasNext(); )
{
String key = keyIt.next();
if (key.equals(searchedKey))
l.add(obj.get(key)); // key found
if (obj.get(key) instanceof JSONObject)
find(obj, searchedKey, l); // recursive search
}
}
public static String read(InputStream is)
{
return null; // TODO: to be written
}
public static void main(String[] args)
{
String searchedKey = args[0];
String content = read(System.in); // Read all the content given to System.in, must be implemented
JSONObject obj = new JSONObject(content);
List<Object> l = new ArrayList<Object>();
find(obj, searchedKey, l);
for (Object a: l)
System.out.println(a); // Call the toString() method of a
}
}
Bibliothèque Jackson
- Jackson : bibliothèque Java spécialisée pour la sérialisation JSON
- Support possible d'autres formats que le JSON : BSON, YAML, XML, CSV...
-
Plus de fonctionnalités que org.json :
- API pour lecture événementielle (approche par curseur)
- Mapping automatique d'objets JSON en objets Java
Mapping automatique avec ObjectMapper de Jackson
package fr.upem.jacosa.serialization;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.util.Map;
public class JacksonBook {
record Book(String title, int price) {}
public static void main(String[] args) throws JsonProcessingException {
var jsonMapper = new ObjectMapper();
var yamlMapper = new ObjectMapper(new YAMLFactory());
// try to jsonify a book and reload it
Book b1 = new Book("Jackson's test", 1000);
String b1json = jsonMapper.writeValueAsString(b1);
System.out.println(b1json);
Book b1reloaded = jsonMapper.readValue(b1json, Book.class); // specifying the class is compulsory
System.out.println("Are b1 and b1reloaded the same? " + b1.equals(b1reloaded));
System.out.println(yamlMapper.writeValueAsString(b1));
// work now with a map of books
var bookMap = Map.of("Useful book", b1,
"Interesting book", new Book("The Art of Computer Programming", 2000),
"Useless book", new Book("The book of vacuum", 0));
String bookMapJson = jsonMapper.writeValueAsString(bookMap);
String bookMapYaml = yamlMapper.writeValueAsString(bookMap);
System.out.println("JSON:\n" + bookMapJson);
System.out.println("YAML:\n" + bookMapYaml);
var bookMapJsonReloaded = jsonMapper.readValue(bookMapJson, new TypeReference<Map<String, Book>>() {});
var bookMapYamlReloaded = yamlMapper.readValue(bookMapYaml, new TypeReference<Map<String, Book>>() {});
System.out.println("Are bookMap and bookMapJsonReloaded the same? " + bookMap.equals(bookMapJsonReloaded));
System.out.println("Are bookMap and bookMapYamlReloaded the same? " + bookMap.equals(bookMapYamlReloaded));
}
}
Fichiers properties
Configuration d'une application
Comment indiquer des informations de configuration pour une application ?
- Directement en dur dans le code : à ne jamais faire
- Regroupement de constantes (public static final ...) : mieux mais nécessite quand même une recompilation
- Passage d'arguments au programme (tableau args de main) : pertinent pour les paramètres les plus importants
- Utilisation de variables d'environnement : \\
- Map<String, String> envMap = System.getenv()
- Fichier de configuration : à privilégier si beaucoup de paramètres existent
Format de fichier de configuration
Expressif (types supportés), éditable et lisible facilement par un humain
- Création d'un format ad-hoc (avec un DSL) : utile si le paramétrage est complexe
- Utilisation d'un format clé-valeur : pour des besoins plus légers
Fichier clé/valeur .properties
# Un petit commentaire clé1 = valeur1 clé2 = valeur2 ... cléI = valeur I \ sur deux lignes ... cléN = une valeur avec un caractère unicode \u2190
-
Limitations
- Les clés et valeurs ne sont pas typables (String)
- Le fichier est représenté en ISO-8859-1 : les caractères Unicode doivent être déspécialisés
Remarque : il existe une version XML des fichiers properties
Transférons un fichier .properties dans une Map
package fr.upem.jacosa.serialization;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class PropertiesReader
{
// Tutorial :
// https://docs.oracle.com/javase/tutorial/essential/environment/properties.html
public static Map<String, String> readProperties(String filepath) throws IOException
{
Properties p = new Properties();
FileInputStream in = new FileInputStream(filepath);
p.load(in);
in.close();
Map<String, String> map = new HashMap<String, String>();
for (Object key: p.keySet())
map.put(key.toString(), p.getProperty(key.toString()));
return map;
}
}