image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Sérialisation

  1. Introduction
    1. Sérialisation ?
    2. Familles de formats de sérialisation
    3. Quelques formats usuels
    4. Formats dédiés
    5. Approches d'API de lecture/écriture
  2. Sérialisation Java
    1. Sérialisation automatique
      1. Caractéristiques de la sérialisation Java
      2. Sérialisons un graphe d'objets
      3. Graphe d'objet sérialisé
      4. Résultat de la sérialisation
      5. Rechargeons les livres sérialisés
    2. Sérialisation avancée
      1. Plus de contrôle avec Externalizable
      2. Sérialisons un livre en compressant son titre
      3. Quelques remarques pour approfondir
  3. Manipulation de données en XML
    1. Le format XML
      1. Un peu de XML
      2. Exemple de document XML
    2. Document Object Model (DOM)
      1. API Document Object Model
      2. Initialisation d'analyseur DOM
      3. Les principaux noeuds
      4. Les autres noeuds
      5. Informations sur un noeud
      6. Récupérons tous les titres de livres
      7. Modification d'un arbre
      8. Cas particulier des documents HTML
    3. SAX
      1. Simple API for XML (SAX) : org.xml.sax
      2. Référencement des balises d'un fichier XML avec SAX
    4. StAX
      1. API StAX
      2. Approche par curseur
      3. Affichage d'événements avec l'approche par curseur
      4. Recherche de livres d'un auteur donné
  4. Manipulation de données en JSON
    1. Présentation
      1. Bibliothèque JSON
    2. Une bibliothèque en JSON
    3. Quelques exemples avec org.json
      1. Arborescence de fichiers en JSON
        1. Niveau 1 : liste des fichiers enfant d'un répertoire
        2. Niveau 2 : arborescence complète d'un répertoire
        3. Résultats
      2. Recherche d'une clé dans un fichier JSON
    4. Bibliothèque Jackson
    5. Mapping automatique avec ObjectMapper de Jackson
  5. Fichiers properties
    1. Configuration d'une application
    2. Format de fichier de configuration
    3. Fichier clé/valeur .properties
    4. Transférons un fichier .properties dans une Map

Introduction

Sérialisation ?

Familles de formats de sérialisation

Quelques formats usuels

Formats dédiés

Approches d'API de lecture/écriture

Sérialisation Java

Sérialisation automatique

Caractéristiques de la sérialisation Java

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é

Graphe d'objet sérialisé

Résultat de la sérialisation

Sérialisation binaire

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

Les exceptions * héritent de IOException

Sérialisation avancée

Plus de contrôle avec Externalizable

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

Manipulation de données en XML

Le format XML

Un peu de XML

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>

Arbre XML de l'exemple précédent

Document Object Model (DOM)

API Document Object Model

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

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 :

Là encore, la Javadoc est utile !

Cas particulier des documents HTML

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)

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 par curseur

☞ 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é

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 :

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 :

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 :

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

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 ?

Format de fichier de configuration

Expressif (types supportés), éditable et lisible facilement par un humain

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

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;
	}
}