Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Coder un moteur de sérialisation

Introduction à la réflexion Java JavaBeans et l'introspection



Accès rapide :
La vidéo
Qu'est-ce que la sérialisation ?
Sérialisation/désérialisation JSON de données simples
Sérialisation de données simples
Désérialisation de données simples
Sérialisation/désérialisation JSON de tableaux et de collections
Sérialisation de tableaux et de collections
Désérialisation de tableaux et de collections
Sérialisation/désérialisation JSON d'un objet Java
Sérialisation d'objets
Désérialisation d'objets à partir d'un flux JSON
Conclusion

La vidéo

Cette vidéo vous montre comment coder un moteur de sérialisation/désérialisation, au format JSON, en utilisant la réflexion Java.


Coder un moteur de sérialisation grâce à la réflexion Java

Qu'est-ce que la sérialisation ?

En POO (Programmation Orientée Objet), la sérialisation est le mécanisme qui permet de transformer un objet (quelle que soit sa complexité) en un flux séquentiel d'octets. Cela permet notamment de transférer un objet sur une machine distante ou encore de sauvegarder un objet dans un fichier. L'opération inverse, la désérialisation, est le mécanisme qui permet de restructurer un objet à partir d'un flux séquentiel d'octets.

Java propose déjà des mécanismes de sérialisation et de désérialisation. Pour autant, et dans le but d'apprendre à utiliser le moteur de réflexion, nous allons coder notre propre système de sérialisation/désérialisation au format JSON. Effectivement, pour pouvoir sauvegarder n'importe quelle instance Java dans un format JSON, il est nécessaire de pouvoir en comprendre sa structure.

Comme proposé, nous allons travailler avec le format JSON (JavaScript Object Notation). C'est un format très simple, basé sur la syntaxe de JavaScript (et donc un peu de Java), concurrent du format XML. Une paire d'accolades représente un objet et chaque attribut y apparaît sous forme d'une association clé/valeur. Voici un exemple de flux JSON contenant les informations d'un article (pour un site de vente en ligne, pourquoi pas).

{
    "idArticle": 1,
    "description": "an article",
    "brand": "a brand",
    "price": 15 
}

Sérialiser un objet Java quelconque en JSON n'est pas si compliqué que cela, mais ce n'est pas complétement simple non plus. C'est pour cette raison que nous allons travailler en plusieurs étapes.

Sérialisation/désérialisation JSON de données simples

Dans un premier temps, nous ne considérerons que des données simples telles que les types primitifs Java ou les chaînes de caractères. Comme nous l'avons précisé dans certains chapitres précédents, les types primitifs ne sont pas des objets, mais peuvent quand même être contenue par des instances des classes enveloppantes (wrapper classes ; classe Integer pour le type int, ...). On parle d'auto-boxing pour l'acte d'envelopper un type primitif dans une classe enveloppante, et d'unboxing pour l'opération inverse.

Dans un souci de simplification, je ne vais pas faire jouer la surcharge, mais plutôt utilisé l'auto-boxing, l'unboxing et le polymorphisme pour gérer les types primitifs.

Sérialisation de données simples

Le code suivant gère l'écriture de données simple dans un fichier texte au format JSON.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
package fr.koor.json;
            
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;


public class SerializationEngine {
    
    public static void writeObject( Object object, PrintWriter writer ) throws Exception {
        Class<?> metadata = object.getClass();
        if ( metadata == Byte.class ||
             metadata == Short.class ||
             metadata == Integer.class ||
             metadata == Long.class ||
             metadata == Float.class ||
             metadata == Double.class ||
             metadata == Boolean.class ) {
            writer.print( "" + object );
        } else if ( metadata == String.class || metadata == Character.class ) {
            writer.print( "\"" + object + "\"" );
        } else {
            throw new Exception( "Not actually implemented" );
        }
    }
    
    
    public static void main( String[] args ) throws Exception {
        
        String file = "./file.json";
        
        try ( PrintWriter writer = new PrintWriter( file ) ) {
            //SerializationEngine.writeObject( 3, writer );
            //SerializationEngine.writeObject( 3.1415, writer );
            SerializationEngine.writeObject( "Hello", writer );
        }
    
    }
    
}
Sérialisation de données simples au format JSON
JSON ne proposant pas d'équivalent au type char de Java. Nous transformerons donc les char Java en chaînes de caractères JSON. Pour rappel, la classe enveloppante du type char est java.lang.Character.

Désérialisation de données simples

Il nous faut maintenant coder l'opération inverse : la relecture de données typées à partir d'un fichier JSON. Par symétrie avec l'opération de sérialisation, je vais proposer une méthode de désérialisable travaillant sur un Reader. Pour autant, cette méthode s'appuiera sur une seconde méthode opérant sur une instance de type java.util.Scanner : cela nous simplifiera grandement la tâche pour décoder les données du flux JSON.

la classe java.util.Scanner permet de sortir des éléments syntaxiques (des tokens) d'un flux textuel en se basant sur des expressions régulières.

Bien que la classe Scanner expose directement des méthodes d'extraction de données basées sur de types primitifs, nous n'allons pas les utiliser. La raison principale est que ces méthodes nécessitent un séparateur (un blanc) à la suite de la donnée à extraire. Cela nous posera des problèmes un peu plus tard. A la place, nous allons utiliser la méthode findInLine qui accepte une expression régulière pour qualifier la donnée à extraire.

Ajouter ces deux méthodes à votre classe SerializationEngine.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
private static Object readObject( Class<?> metadata, Scanner scanner ) throws Exception {
    if ( metadata == Byte.class || metadata == byte.class ) {
        return Byte.parseByte( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Short.class || metadata == short.class  ) {
        return Short.parseShort( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Integer.class || metadata == int.class ) {
        return Integer.parseInt( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Long.class || metadata == long.class  ) {
        return Long.parseLong( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Float.class || metadata == float.class  ) {
        return Float.parseFloat( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
    } else if ( metadata == Double.class || metadata == double.class ) {
        return Double.parseDouble( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
    } else if ( metadata == Boolean.class || metadata == boolean.class  ) {
        return Boolean.parseBoolean( scanner.findInLine( "true|false" ) );
    } else if ( metadata == String.class || metadata == Character.class || metadata == char.class ) {
        String value = scanner.findInLine( "\".*?\"" );
        return value.substring( 1, value.length()-1 );
    } else {
        throw new Exception( "Not actually implemented" );
    }
}

// J'introduis ici la généricité pour plus de confort.
@SuppressWarnings( "unchecked" )
public static <T> T readObject( Class<T> metadata, Reader reader ) throws Exception {
    try ( Scanner scanner = new Scanner( reader ) ) {
        scanner.useLocale(Locale.US);    // double stocké avec le caractère . (et non la virgule)
        return (T) readObject( metadata, scanner );    
    }
}
Désérialisation de données simples

Et modifiez maintenant votre méthode main ainsi afin de tester quelques cas.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
public static void main( String[] args ) throws Exception {
    
    String file = "./file.json";
    
    try ( FileReader reader = new FileReader( new File( file ) ) ) {
        //double data = SerializationEngine.readObject( Double.class, reader );
        String data = SerializationEngine.readObject( String.class, reader );
        System.out.println( data );
    }
            
}
Modification du main pour tester la relecture des données

Sérialisation/désérialisation JSON de tableaux et de collections

Nous allons maintenant modifier notre code afin de permettre la gestion des tableaux Java ou des collections compatibles avec l'interface java.util.List. Pour rappel, le format JSON introduit un tableau de données via la syntaxe [ ]. Voici un exemple de flux JSON contenant un tableau de chaînes de caractères.

["Java", "Python", "C#", "C++"]

Sérialisation de tableaux et de collections

Dans le but de nous simplifier la vie (ça ne reste qu'un prototype), nous allons gérer quasiment de la même manière les tableaux et les collections. Pour ce faire nous allons transformer les tableaux Java en collections et nous les sérialiserons dans le flux. Mais nous avons une difficulté. Il n'est pas trivial de transformer un tableau basé sur un type primitif en une collection de wrapper. Je propose de créer une méthode utilitaire prenant en charge ce besoin : en voici son code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
package fr.koor.json;

import java.util.ArrayList;
import java.util.List;

public class ArrayUtils {

    public static List<Object> toObjectList( Object array ) throws Exception {
        List<Object> result = new ArrayList<>();

        if ( array instanceof byte[] ) {
            for ( byte value : (byte[]) array ) result.add( value );
        } else if ( array instanceof short[] ) {
            for ( short value : (short[]) array ) result.add( value );
        } else if ( array instanceof int[] ) {
            for ( int value : (int[]) array ) result.add( value );
        } else if ( array instanceof long[] ) {
            for ( long value : (long[]) array ) result.add( value );
        } else if ( array instanceof float[] ) {
            for ( float value : (float[]) array ) result.add( value );
        } else if ( array instanceof double[] ) {
            for ( double value : (double[]) array ) result.add( value );
        } else if ( array instanceof boolean[] ) {
            for ( boolean value : (boolean[]) array ) result.add( value );
        } else if ( array instanceof char[] ) {
            for ( char value : (char[]) array ) result.add( value );
        } else if ( array.getClass().isArray() ) {
            for ( Object value : (Object[]) array ) result.add( value );
        } else {
            throw new Exception( "Not supported" );
        }
        
        return result;
    }

}
Méthode utilitaire de transformation de tableaux en collection

Nous pouvons maintenant modifier le code de notre méthode de sérialisation pour y ajouter la gestion des collections et des tableaux. Pour chaque élément de l'ensemble, nous ré-invoquerons récursivement la méthode de sérialisation. Voici le code cette méthode.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
public static void writeObject( Object object, PrintWriter writer ) throws Exception {
    Class<?> metadata = object.getClass();
    if ( metadata == Byte.class ||
         metadata == Short.class ||
         metadata == Integer.class ||
         metadata == Long.class ||
         metadata == Float.class ||
         metadata == Double.class ||
         metadata == Boolean.class ) {
        // --- On gère les types de bases ---
        writer.print( "" + object );
    } else if ( metadata == String.class || metadata == Character.class ) {
        // --- On gère les chaînes de caractères ---
        writer.print( "\"" + object + "\"" );
    } else if ( metadata.isArray() || object instanceof List ) {
        // --- On gère les tableaux et les collections ---
        @SuppressWarnings("rawtypes") 
        List collection = object instanceof List 
                ? (List) object : ArrayUtils.toObjectList( object ) ;
        int size = collection.size();
        int i = 0;

        writer.print( "[" );
        for( Object value : collection ) {
            writeObject( value, writer );
            if ( i++ < size - 1 ) writer.print( "," );
        }
        writer.print( "]" );
    } else {
        // --- On gère les objets Java ---
        throw new Exception( "Not actually implemented" );
    }
}
Nouvelle version de la méthode de sérialisation

Il ne reste plus qu'à tester la sérialisation d'un tableau de chaînes de caractères.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
public static void main( String[] args ) throws Exception {
    
    String file = "./file.json";
    
    try ( PrintWriter writer = new PrintWriter( file ) ) {
        //double [] values = { 14.2, 15.3, 16.4 };
        //SerializationEngine.writeObject( values, writer );
        
        //List<String> languages = Arrays.asList( "Java", "Python", "C#", "C++" );
        //SerializationEngine.writeObject( languages, writer );
        
        String [] languages = { "Java", "Python", "C#", "C++" };
        SerializationEngine.writeObject( languages, writer );
    }

}
Test de notre méthode de sérialisation JSON

Et voici le contenu du fichier produit par cet exemple.

["Java","Python","C#","C++"]

Désérialisation de tableaux et de collections

L'opération inverse (la relecture de tableaux ou de collections à partir d'un flux JSON) est un petit peu plus complexe et va bien au-delà de ce que je veux vous montrer ici. Je préfère donc ignorer cette partie.

Sérialisation/désérialisation JSON d'un objet Java

En guise d'exemple, je vous propose de travailler avec une classe Article : elle contient quatre attributs (un entier, deux chaînes de caractères et un flottant). En voici son code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
class Article {
    
    private int idArticle;
    private String description;
    private String brand;
    private double price;
    
    // Requis pour le moteur de sérialisation/désérialisation JSON
    public Article() {
        this( 1, "unknown", "unknown", 0 ); 
    }
    
    public Article( int idArticle, String description, String brand, double price ) {
        this.idArticle = idArticle;
        this.description = description;
        this.brand = brand;
        this.price = price;
    }

    @Override public String toString() {
        return "Article [idArticle=" + idArticle + ", description=" + 
               description + ", brand=" + brand + ", price=" + price + "]";
    }
    
}
Notre classe d'objets à sérialiser.

Comme vous l'aurez remarqué, les attributs de cette classe sont privés : il va nous falloir forcer l'accès à ces attributs via le moteur de réflexion. Pour chaque attribut, nous rappellerons récursivement la méthode sérialisation pour envoyé sa valeur dans le flux JSON.

Sérialisation d'objets

Je vous propose de modifier ainsi le code de la méthode de sérialisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
public static void writeObject( Object object, PrintWriter writer ) throws Exception {
    Class<?> metadata = object.getClass();
    if ( metadata == Byte.class ||
         metadata == Short.class ||
         metadata == Integer.class ||
         metadata == Long.class ||
         metadata == Float.class ||
         metadata == Double.class ||
         metadata == Boolean.class ) {
        // --- On gère les types de bases ---
        writer.print( "" + object );
    } else if ( metadata == String.class || metadata == Character.class ) {
        // --- On gère les chaînes de caractères ---
        writer.print( "\"" + object + "\"" );
    } else if ( metadata.isArray() || object instanceof List ) {
        // --- On gère les tableaux et les collections ---
        @SuppressWarnings("rawtypes") 
        List collection = object instanceof List 
                ? (List) object 
                : ArrayUtils.toObjectList( object ) ;
        int size = collection.size();
        int i = 0;

        writer.print( "[" );
        for( Object value : collection ) {
            writeObject( value, writer );
            if ( i++ < size - 1 ) writer.print( "," );
        }
        writer.print( "]" );
    } else {
        // --- On gère les objets Java ---
        writer.write( "{" );
        Field [] fields = metadata.getDeclaredFields();
        for ( int i=0; i<fields.length; i++ ) {
            Field field = fields[i];
            field.setAccessible( true );
            writer.write( String.format( "\"%s\": ", field.getName() ) );
            writeObject( field.get( object ), writer );
            if ( i < fields.length-1 ) writer.write( ", " );
        }
        writer.write( "}" );
    }
}
Sérialisation d'objets Java

Modifions la méthode main afin de tester l'envoi d'un objet dans le flux JSON.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
public static void main( String[] args ) throws Exception {
    String file = "./file.json";
    
    try ( PrintWriter writer = new PrintWriter( file ) ) {
        Article article = new Article();
        SerializationEngine.writeObject( article, writer );   
    }
}
Tentative d'écriture d'un objet

Voici le contenu du fichier JSON produit par cet exemple.

{"idArticle": 1, "description": "unknown", "brand": "unknown", "price": 0.0}

Nous l'avons vu, la méthode writeObject est récursive. Donc si une classe définie des attributs basés sur d'autres classes, normalement, la grappe d'objets doit être envoyée sur le flux. Considérons cette classe de démonstration (le tableau et juste là pour tester ce cas particulier).

 1 
 2 
 3 
 4 
 5 
class CommandLine {
    private Article article = new Article();
    private int [] data = { 10, 20, 30 };
    private int quantity;
}
Exemple d'objets composés

Modifions de nouveau le main afin d'envoyer une instance de la classe CommandLine.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
public static void main( String[] args ) throws Exception {
    String file = "./file.json";
    
    try ( PrintWriter writer = new PrintWriter( file ) ) {
        CommandLine commandLine = new CommandLine();
        SerializationEngine.writeObject( commandLine, writer );
    }        
}
Tentative d'écriture d'un objet

Et voici le contenu du nouveau fichier JSON produit.

{"article": {"idArticle": 1, "description": "unknown", "brand": "unknown", "price": 0.0}, "data": [10,20,30], "quantity": 0}

Désérialisation d'objets à partir d'un flux JSON

Nous allons maintenant voir comment recharger un objet à partir d'un flux JSON : je vous propose de modifier le code de la méthode readObject ainsi.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
private static Object readObject( Class<?> metadata, Scanner scanner ) throws Exception {
    if ( metadata == Byte.class || metadata == byte.class ) {
        return Byte.parseByte( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Short.class || metadata == short.class  ) {
        return Short.parseShort( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Integer.class || metadata == int.class ) {
        return Integer.parseInt( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Long.class || metadata == long.class  ) {
        return Long.parseLong( scanner.findInLine( "[0-9]+" ) );
    } else if ( metadata == Float.class || metadata == float.class  ) {
        return Float.parseFloat( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
    } else if ( metadata == Double.class || metadata == double.class ) {
        return Double.parseDouble( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
    } else if ( metadata == Boolean.class || metadata == boolean.class  ) {
        return Boolean.parseBoolean( scanner.findInLine( "true|false" ) );
    } else if ( metadata == String.class || metadata == Character.class || metadata == char.class ) {
        String value = scanner.findInLine( "\".*?\"" );
        return value.substring( 1, value.length()-1 );
    } else if ( metadata.isArray() || List.class.isAssignableFrom( metadata ) ) {            
        // --- Désérialisation de tableaux ou de collections ---
        throw new Exception( "Not actually implemented" );
    } else {
        // --- Désérialisation d'un objet ---
        Object object = metadata.newInstance();
        scanner.findInLine( "\\{\\s*" );
        while( ! scanner.hasNext( "\\s*\\}" ) ) {
            String attributeName = (String) readObject( String.class, scanner );
            scanner.findInLine( ":\\s*" );                
            Field field = metadata.getDeclaredField( attributeName );
            field.setAccessible( true );
            Object value = readObject( field.getType(), scanner );
            field.set( object, value );
        }
        scanner.findInLine( "\\s*\\}" );
        return object;
    }
}
Désérialisation d'objets à partir d'un flux JSON

Et voici un exemple d'utilisation de notre méthode de désérialisation d'objet pour recharger un objet de type Article.

 1 
 2 
 3 
 4 
try ( FileReader reader = new FileReader( new File( file ) ) ) {
    Article article = SerializationEngine.readObject( Article.class, reader );
    System.out.println( article );
}
Exemple d'utilisation de notre méthode de désérialisation

Conclusion

Bien entendu, ce moteur de sérialisation JSON reste perfectible (ce n'est qu'une démonstration) et je vous invite à poursuivre son amélioration. Les points majeurs à améliorer sont :

Cela sera un très bon exercice pour vous de poursuivre son implémentation. N'hésitez pas à proposer vous améliorations dans les commentaires ci-dessous afin d'en faire profiter les autres lecteurs.

Sachez aussi qu'il existe déjà de nombreuses implémentations de moteurs de sérialisation prêtes à l'emploi : on peut citer, en autres, Jackson, Gson, Genson ... Personnellement, j'utilise beaucoup Genson qui est très simple d'emploi : http://genson.io/.

Pour finir, voici le code complet de l'exemple proposé dans ce tuto.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
 100 
 101 
 102 
 103 
 104 
 105 
 106 
 107 
 108 
 109 
 110 
 111 
 112 
 113 
 114 
 115 
 116 
 117 
 118 
 119 
 120 
 121 
 122 
 123 
 124 
 125 
package fr.koor.json;

import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;


public class SerializationEngine {
    
    public static void writeObject( Object object, PrintWriter writer ) throws Exception {
        Class<?> metadata = object.getClass();
        if ( metadata == Byte.class ||
             metadata == Short.class ||
             metadata == Integer.class ||
             metadata == Long.class ||
             metadata == Float.class ||
             metadata == Double.class ||
             metadata == Boolean.class ) {
            // --- On gère les types de bases ---
            writer.print( "" + object );
        } else if ( metadata == String.class || metadata == Character.class ) {
            // --- On gère les chaînes de caractères ---
            writer.print( "\"" + object + "\"" );
        } else if ( metadata.isArray() || object instanceof List ) {
            // --- On gère les tableaux et les collections ---
            @SuppressWarnings("rawtypes") 
            List collection = object instanceof List 
                    ? (List) object 
                    : ArrayUtils.toObjectList( object ) ;
            int size = collection.size();
            int i = 0;
    
            writer.print( "[" );
            for( Object value : collection ) {
                writeObject( value, writer );
                if ( i++ < size - 1 ) writer.print( "," );
            }
            writer.print( "]" );
        } else {
            // --- On gère les objets Java ---
            writer.write( "{" );
            Field [] fields = metadata.getDeclaredFields();
            for ( int i=0; i<fields.length; i++ ) {
                Field field = fields[i];
                field.setAccessible( true );
                writer.write( String.format( "\"%s\": ", field.getName() ) );
                writeObject( field.get( object ), writer );
                if ( i < fields.length-1 ) writer.write( ", " );
            }
            writer.write( "}" );
        }
    }

    
    private static Object readObject( Class<?> metadata, Scanner scanner ) throws Exception {
        if ( metadata == Byte.class || metadata == byte.class ) {
            return Byte.parseByte( scanner.findInLine( "[0-9]+" ) );
        } else if ( metadata == Short.class || metadata == short.class  ) {
            return Short.parseShort( scanner.findInLine( "[0-9]+" ) );
        } else if ( metadata == Integer.class || metadata == int.class ) {
            return Integer.parseInt( scanner.findInLine( "[0-9]+" ) );
        } else if ( metadata == Long.class || metadata == long.class  ) {
            return Long.parseLong( scanner.findInLine( "[0-9]+" ) );
        } else if ( metadata == Float.class || metadata == float.class  ) {
            return Float.parseFloat( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
        } else if ( metadata == Double.class || metadata == double.class ) {
            return Double.parseDouble( scanner.findInLine( "[0-9]+(\\.[0-9]+)?" ) );
        } else if ( metadata == Boolean.class || metadata == boolean.class  ) {
            return Boolean.parseBoolean( scanner.findInLine( "true|false" ) );
        } else if ( metadata == String.class || metadata == Character.class || metadata == char.class ) {
            String value = scanner.findInLine( "\".*?\"" );
            return value.substring( 1, value.length()-1 );
        } else if ( metadata.isArray() || List.class.isAssignableFrom( metadata ) ) {
            // --- Désérialisation de tableaux ou de collections ---
            throw new Exception( "Not actually implemented" );
        } else {
            // --- Désérialisation d'un objet ---
            Object object = metadata.newInstance();
            scanner.findInLine( "\\{\\s*" );
            while( ! scanner.hasNext( "\\s*\\}" ) ) {
                String attributeName = (String) readObject( String.class, scanner );
                scanner.findInLine( ":\\s*" );                
                Field field = metadata.getDeclaredField( attributeName );
                field.setAccessible( true );
                Object value = readObject( field.getType(), scanner );
                field.set( object, value );
            }
            scanner.findInLine( "\\s*\\}" );
            return object;
        }
    }
    
    // J'introduis ici la généricité pour plus de confort.
    @SuppressWarnings( "unchecked" )
    public static <T> T readObject( Class<T> metadata, Reader reader ) throws Exception {
        try ( Scanner scanner = new Scanner( reader ) ) {
            scanner.useLocale(Locale.US);    // double stocké avec le caractère . (et non la virgule)
            return (T) readObject( metadata, scanner );    
        }
    }
    
    public static void main( String[] args ) throws Exception {
        
        String file = "./file.json";
        
        try ( PrintWriter writer = new PrintWriter( file ) ) {
        
            SerializationEngine.writeObject( new Article(), writer );
        
        }
    
        try ( FileReader reader = new FileReader( new File( file ) ) ) {
            
            System.out.println( SerializationEngine.readObject( Article.class, reader ) );

        }
                
    }
    
}
Le code complet de notre moteur de sérialisation/désérialisation


Introduction à la réflexion Java JavaBeans et l'introspection