Notions fondamentales de JSON

Les formats de données

Lorsque l’on développe en Javascript, le format de données roi est le JSON (JavaScript Object Notation). Ce format supporte deux types d’entité : les tableaux (ou array) représentés par la notation littérale « [] », et les objets, aussi appelés dictionnaires ou hash représentés par la notation littérale « {} ».

Les tableaux ont une propriété length qui indique le nombre d’éléments qu’ils contiennent. Il est possible d’accéder à ces éléments par le biais d’un indexeur, et on utilise généralement une boucle for pour les parcourir.

var array = [ "Foobar", 42, true ];
for (var i = 0; i < array.length; i++) {
    var item = array[i];
}

Les hash quand à eux, contiennent des propriétés nommées auxquelles il est possible d’accéder directement, soit par une notation pointée, soit par le biais d’un indexeur nommé.

var object = {
    "Lastname":  "Guillot",
    "Firstname": "François"
};
var lastname = object.Lastname;      // Notation pointée
var firstname = object["Firstname"]; // Indexeur nommé

L’accès par indexeur nommé offre l’avantage de pouvoir être utilisé avec une variable contenant le nom de la propriété.

// Même chose que précédemment, mais le nom de la propriété "Firstname" est contenu dans une variable
var propertyName = "Firstname";
var firstname = object[propertyName];

Pour parcourir les propriétés d’un hash, on utilise une boucle for in. Chaque tour de boucle permet d’accéder au nom d’une propriété du hash, permettant ensuite d’accéder à la valeur de celle-ci par le biais de l’indexeur nommé.

var city = {
    "Name": "Marseille",
    "ZipCode": "13000"
};
for (var key in city) {
    // Au premier tour de boucle, key contiendra "Name", et value contiendra "Marseille"
    var value = city[key];
}

Il n’est pas possible de parcourir un tableau avec la boucle for in, car bien qu’à chaque tour de boucle la clef contiendrait une des valeurs contenue par le tableau, elle contiendrait également la valeur « length », car toutes les propriétés d’un objet Javascript sont inspectées.

La boucle for in inspecte également les propriétés d’un objet héritées par prototypage. Si vous ne souhaitez inspecter que les propriétés que porte l’objet lui-même, vous devez tout d’abord contrôler leur origine avec la méthode hasOwnProperty().

for (var key in city) {
    if (city.hasOwnProperty(key)) {
        // La clef contient bien le nom d'une propriété que l'objet city n'a pas reçu par héritage
        var value = city[key];
    }
}

Il est bien entendu possible de mixer les tableaux et les hash, pour concevoir des graphes de données complexes.

// Tableau contenant un hash, un booléen, et un autre tableau
var complexGraph = [
    { "Language": "C#", "Version": "4.5", "Year": 2012, "Creators": [ "Anders Hejlsbjerg" ] },
    true,
    [ 'Test', 12, { "Id": "4BB84287-B919-45DD-98BA-FFBC7D6F7BF2" } ]
];

Écrire du code JSON valide

JSON est un format de données natif du langage Javascript, ce qui signifie que dans ce contexte, il est possible de l’utiliser au-delà de ses possibilités standards. Par exemple, en Javascript on s’autorise généralement à écrire les noms des propriétés des hash sans les encadrer par des double quotes (« ), et on peut placer dans ces mêmes hash des données non littérales, comme des fonctions.

var hashJavascript = {
    Property: function (param) { return param; }
};

Mais la notation JSON a aujourd’hui largement dépassé le cadre du seul langage Javascript, au point d’être utilisée couramment dans de nombreux autres langages. Ceux-ci disposent généralement d’un parseur, qui est très pointilleux quand au respect de la syntaxe. En effet, le JSON a été normalisé, et les moteurs de parsing lèvent une exception si la chaîne de caractères qui leur est soumise ne respecte pas cette norme.

Cette norme est toutefois assez simple à respecter. Les valeurs contenues par les tableaux ou par les propriétés des hash, ne peuvent être que des chaînes de caractères, des nombres entiers ou décimaux, des booléens, la valeur spéciale null, ou bien entendu des sous-tableaux ou des sous-objets. Sont prohibés les fonctions et les dates (ce qui pose souvent problème pour faire circuler des informations de type DateTime).

Autre point important : les noms de propriétés dans les hash doivent être encadrés par des double quotes (« ), pas des simples quotes (‘), et ils doivent encore moins être écrits sans formatage. Cette règle s’applique également aux chaînes de caractères : double quotes (« ) obligatoires. Notez qu’à titre personnel, j’ai tendance à trouver la notation des propriétés avec des double quotes nettement plus lisible.

// Cet objet JSON est mal formé
var wrongObject = {
    prop: 'je m\'appelle "toto"',
    today: new Date(2012, 6, 21, 12, 44, 55, 0)
};

// Cet objet JSON respecte la norme
var rightObject = {
    "prop": "je m'appelle \"toto\"",
    // La date est une chaîne de caractères au format ISO-8601, qui est en train de devenir la nouvelle norme
    "today": "2012-06-21T12:44:55"
};

Moralité : si vous devez faire circuler des données au format JSON entre votre serveur et le client, prenez soin de bien respecter le norme, car un parseur comme la méthode jQuery.parseJSON() rejettera votre chaîne de caractères si celle-ci contient des erreurs de formatage.

Optimiser la récupération des données

Il arrive couramment que l’on doive retrouver un objet JSON parmi un tableau de ceux-ci, à partir de la valeur d’une de ses propriétés; typiquement un Id.

var animals = [
    { "Id": "D8777CD6-5E94-4464-8B3D-474DF4EC99FB", "Name": "Warthog" },
    { "Id": "BCDFEEC7-63F5-44C1-850D-246FB40B470A", "Name": "Hedgehog" },
    { "Id": "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1", "Name": "Fawn" },
    { "Id": "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F", "Name": "Gibbon" }
];

Admettons que nous souhaitons récupérer parmi cette liste d’animaux celui dont l’Id est A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1 (c’est le faon). Une solution consiste à utiliser la méthode utilitaire jQuery.grep().

var idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1";

// Remarquez que jQuery.grep() renvoie un tableau d'éléments respectant le prédicat passé en paramètre
// Comme nous savons qu'il n'y a qu'un seul élément respectant le prédicat, nous prenons directement le premier élément du tableau résultant
var fawn = jQuery.grep(animals, function (item) {
    return item.Id === idFawn;
})[0];

Une autre solution consiste à le faire en Javascript pur. On s’épargne ainsi de boucler sur toutes les valeurs du tableau, puisqu’on peut arrêter la recherche dès lors que le résultat a été trouvé.

var idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1",
    fawn = null;
for (var i = 0; i < animals.length; i++) {
    var animal = animals[i];
    if (animal.Id === idFawn) {
        fawn = animal;
        break;
    }
}

Le problème de ces deux méthodes, c’est qu’elles ne sont en aucun cas optimisées pour les moteurs Javascript. En effet, la solution idéale consiste à utiliser un indexeur nommé sur un hash. Mais pour cela, il faut qu’au départ nos données soient dans un format quelque peu différent.

var animals = {
    "D8777CD6-5E94-4464-8B3D-474DF4EC99FB": { "Name": "Warthog" },
    "BCDFEEC7-63F5-44C1-850D-246FB40B470A": { "Name": "Hedgehog" },
    "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1": { "Name": "Fawn" },
    "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F": { "Name": "Gibbon" }
},
    idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1",
    fawn = animals[idFawn];

Cette méthode est optimale car elle permet d’aller récupérer le faon directement dans la liste des animaux par son id, sans avoir à effectuer la moindre boucle. Dans des cas de manipulations de très grandes quantités de données, les performances peuvent s’en trouver nettement améliorées.

C’est pourquoi il est important que les données soient présentées dans un format adéquat. Ce format peut être élaboré côté serveur, avec d’injecter les données dans la page ou de les envoyer par le biais d’une requête Ajax. Pour effectuer ce type d’opération, j’ai généralement recours à la très bonne librairie JSON.NET, qui est maintenant fournie en standard dans les projets ASP.NET Web API (qui l’utilise lui-même pour toutes les opérations de manipulation de JSON).

// Exemple de création d'un objet JSON en C# avec JSON.NET
// N'oubliez pas d'ajouter "using Newtonsoft.Json.Linq;"
Animal[] animals = new Animal[]
{
    new Animal { Id = Guid.Parse("D8777CD6-5E94-4464-8B3D-474DF4EC99FB"), Name = "Warthog" },
    new Animal { Id = Guid.Parse("BCDFEEC7-63F5-44C1-850D-246FB40B470A"), Name = "Hedgehog" },
    new Animal { Id = Guid.Parse("A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1"), Name = "Fawn" },
    new Animal { Id = Guid.Parse("2EBAC3FC-C03E-4263-95D2-1B26E0260C0F"), Name = "Gibbon" }
};

JObject objectJson = new JObject();

foreach (Animal animal in animals)
    objectJson.Add(animal.Id.ToString(), JObject.FromObject(animal));

// Résultat sous forme de chaîne de caractères, non-indenté
string json = objectJson.ToString(Formatting.None);

On peut aussi transformer le format des données côté client, opération coûteuse mais qui peut être ensuite contrebalancée par le gain de performances obtenu par l’utilisation des indexeurs nommés.

// Exemple de transformation du tableau d'animaux vers un hash d'animaux
var animals = [
    { "Id": "D8777CD6-5E94-4464-8B3D-474DF4EC99FB", "Name": "Warthog" },
    { "Id": "BCDFEEC7-63F5-44C1-850D-246FB40B470A", "Name": "Hedgehog" },
    { "Id": "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1", "Name": "Fawn" },
    { "Id": "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F", "Name": "Gibbon" }
], newAnimals = {};
for (var i = 0; i < animals.length; i++) {
    var animal = animals[i];
    newAnimals[animal.Id] = animal;
}

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s