Le facteur J sonne toujours deux fois
Par Clochix le lundi 4 mai 2009, 01:57 - Technoweb - Lien permanent
Suite du voyage au pays des accolades et des crochets avec les schémas JSON.
C'est un billet de Aaron Boodman, un des développeurs de Google Chrome, qui m'a fait découvrir il y a quelques jours JSON Schema, une proposition de spécification permettant de définir la structure de données formatées en JSON, à l'image de ce que XML Schema, RelaxNG ou les bonnes vieilles DTD permettent par exemple en XML.
Le schéma
Les différentes versions de la proposition de norme, ainsi que des exemples sont disponibles dans les pages web associées à la liste de discussion. On trouvera de nombreuses informations complémentaires sur le site de Kris Zyp, qui est à l'origine de la proposition.
Un schéma JSON définit les propriétés de l'objet sérialisé et les contraintes sur leurs valeurs. La description d'une structure JSON est codée en JSON, elle est donc elle-même validable au moyen de ce schéma.
Pour chaque propriété, le schéma permet de spécifier:
- son type : simple (chaîne, nombre, booléen etc) ou
composé. Par exemple, si une propriété peut contenir un entier ou un booléen,
on indiquera
{"type":["boolean","integer"]}. Le type peut être précisé en faisant référence à un format parmi ceux pré-définis ici. La liste des formats pré-définis est longue: date, heure, uri, adresse mail, etc; - si la propriété est un objet, la liste de ses propriétés;
- si la propriété est un tableau, le schéma permettant de valider chacun de ses items;
- on peut indiquer des relations entre les propriétés, par
exemple que l'une est obligatoire si une autre est renseignée. Dans l'exemple
{"state":{optional:true},"town":{"requires":"state",optional:true}}, la ville et l'état sont optionnels, mais si la ville est présente l'état devra l'être également; - différentes caractéristique: présence obligatoire, schéma pour valider d'éventuelles propriétés additionnelles, etc. On peut également signifier que la valeur d'une propriété devra être unique pour tout l'objet. C'est typiquement le cas d'une propriété id;
- les valeurs autorisées pour une propriété sont définissables de plusieurs manières: en précisant un intervalle, les longueurs minimales et maximales pour les chaînes de caractères, une énumération de valeurs autorisées, un masque utilisant des expression régulière... Pour un tableau, on pourra préciser son nombre minimum et maximum d'items;
- le schéma contient enfin quelques propriétés ignorées par la validation, mais qui servent à définir plus précisément l'objet et pourront être utilisées dans d'autres contextes: la visibilité de la propriété (publique ou privée), est-ce qu'il est permis de la modifier, est-ce que sa valeur est volatile ou devrait persister...
Un mécanisme d'héritage permet de dériver de plusieurs autres schémas. De plus, JSON Schema utilise la syntaxe JSON Referencing, une autre proposition de Kris Zyp dont j'ai parlé dans mon précédent billet. On peut donc dans un schéma faire référence à un autre schéma.
La proposition n'est pas encore finalisée, mais a déjà suscité des implémentations dans de nombreux langages, en JavaScript évidemment, en Python et depuis peu en PHP, grâce au travail de Bruno Reis.
Un petit exemple de schéma
Cet exemple, extrait de la documentation définit un évènement de calendrier:
{"description":"A representation of an event",
"type":"object",
"properties":{
"dtstart":{
"format": "date-time",
"type": "string",
"description": "Event starting time"
},
"summary":{
"type": "string"
},
"location":{
"type": "string",
"optional": true
},
"url":{
"type":"string",
"format": "url",
"optional": true
},
"dtend":{
"format": "date-time",
"type": "string",
"description": "Event ending time",
"optional": true
},
"duration":{
"format": "date",
"type": "string",
"description": "Event duration",
"optional":true
},
"rdate":{
"format": "date-time",
"type": "string",
"description": "Recurrence date",
"optional": true
},
"rrule":{
"type": "string",
"description": "Recurrence rule",
"optional": true
},
"category":{
"type": "string",
"optional": true
},
"description":{
"type": "string",
"optional": true
},
"geo":{
"type": "object",
"properties": {"$ref":"http://json-schema.org/geo.properties"},
"optional": true
}
}
}
Exemples d'applications
JSON Schema à tous les étages
JSON Schema permet en fait bien plus que valider format de données. Certaines de ses propriétés n'ont pas grand chose à voir avec de la validation. C'est un schéma pour décrire de manière relativement exhaustive n'importe quel objet manipulable dans un traitement informatique. Dès lors, on peut l'utiliser comme format de description des objets métier d'une application, un format aisé à utiliser à tous les niveaux. Dans le cadre d'une architecture MVC, on pourra par exemple l'utiliser dans les vues pour générer dynamiquement les formulaires de gestion de l'objet. Passé au client, il pourra être utilisé pour réaliser une première validation en JavaScript des valeurs saisies par l'utilisateur. Côté serveur à nouveau, dans le contrôleur, on re-validera les saisies au moyen du même shéma, ce qui garantit la cohérence des tests entre le client et le serveur. En cas d'import de données, c'est encore lui qui contrôlera la validité des informations importées. Et bien sûr, dans la couche Modèle, le schéma servira de base au mapping avec la base de données.
La plupart des frameworks proposent certes déjà des mécanismes similaires, en décrivant les objets métier en XML, voire en YAML comme dans Symfony. Mais l'alternative JSON Schema ma parait séduisante.
Exemple funky
Le billet d'Aaron Boodman m'a donné une autre idée d'utilisation de JSON Schema, pour contrôler les paramètres d'une méthode.
Les paramètres nommés sont une fonctionnalité bien pratique de certains langages, Python par exemple. Ils permettent lors de l'appel d'une méthode de spécifier ses paramètres en les nommant, et non à partir de leur position. Pratique lorsque la signature d'une méthode commence à s'allonger. Pour pallier l'absence de cette fonctionnalité, j'ai l'habitude de remplacer les longues listes de paramètres par un unique objet en JavaScript, ou un tableau en PHP. La signature de la méthode perd en clarté (et l'assistance à la saisie des IDE est également à la peine), mais je trouve le code d'appel plus lisible:
// à
$foo->bar('toto', 'titi', 'tata', 'tutu');
// je préfère
foo.bar({firstname; 'toto', lastname: 'titi'});
$foo.bar(array(firstname; 'toto', lastname: 'titi'));
foo.bar(lastname= 'titi', firstname = 'toto');
Autre inconvénient de PHP et JavaScript, ils sont faiblement typés. Préciser le type des paramètres est impossible en JS et seulement possible pour les objets en PHP. Avec JSON Schema, et en passant les paramètres sous forme de tableau ou d'objets, on va pouvoir pour le même prix les valider.
Attention, le code qui suit va sans doute faire hurler quelques-uns tant il utilise un missile de croisière pour écraser une mouche. Mais comme vous le diront nombre de militaires, c'est bien plus fun qu'avec une tapette. Dans cet exemple, avant d'appeler une fonction, je valide chacun de ses paramètres au moyen d'un schéma JSON. Accessoirement, je retrouve le type des paramètres grâce aux annotations. Merci de ne pas chercher la petite bête, le code a été écrit sur le coin d'une table juste pour illustrer mon propos[1].
<?php
require_once 'JsonSchema.php';
require_once 'JsonSchemaUndefined.php';
Class toto{
/**
* my test function
*
* @param param JSON my_schema
*
* @return void
*/
public function titi($param){
echo "bonjour monde cruel\n";
}
}
/**
* Create an object from an array
*/
function toObject($arr){
$res = new StdClass();
if (is_array($arr)) {
foreach ($arr as $k => $v) {
$res->$k = $v;
}
return $res;
} else {
return $arr;
}
}
// calendar event description, taken from http://groups.google.com/group/json-schema/web/common-json-schema-definitions
$my_schema = '
{"description":"A representation of an event",
"type":"object",
"properties":{
"dtstart":{"format":"date-time","type":"string","description":"Event starting time"},
"summary":{"type":"string"},
"location":{"type":"string","optional":true},
"url":{"type":"string","format":"url","optional":true},
"dtend":{"format":"date-time","type":"string","description":"Event ending time","optional":true},
"duration":{"format":"date","type":"string","description":"Event duration","optional":true},
"rdate":{"format":"date-time","type":"string","description":"Recurrence date","optional":true},
"rrule":{"type":"string","description":"Recurrence rule","optional":true},
"category":{"type":"string","optional":true},
"description":{"type":"string","optional":true},
"geo":{"type":"object","properties":{"$ref":"http://json-schema.org/geo.properties"},"optional":true}
}
}
';
$method = new ReflectionMethod('toto', 'titi');
$comment = $method->getDocComment();
// use a regular expression to extract the parameters type from the method comment
preg_match_all("/^\s*\*\s*@param\s*(\[^\s\]+)\s*(\[^\s\]+)\s*(\[^\s\]+)/m", $comment, $params, PREG_SET_ORDER);
foreach ($params as $param){
if (count($param) $gt; 2){
if ($param[2] == 'JSON') {
if (isset($$param[3])) {
$schema = json_decode($$param[3]);
}
}
}
}
// some unit tests
if (!empty($schema)){
$tests = array(
// invalid
'toto',
// invalid
array(),
// invalid
array('dtstart' => '', 'summary' => '', 'geo' => ''),
// still invalid but should validate as the validator
// is in beta and not very accurate
array('dtstart' => '1970-01-01T00:00:00',
'summary' => 'Epoch',
'geo' => toObject(array()))
);
foreach ($tests as $n => $test) {
echo "\n\nTEST n°$n\n\n";
var_dump($test);
$res = JsonSchema::validate(toObject($test), $schema);
if ($res->valid){
toto::titi($param);
} else {
foreach ($res->errors as $error) {
echo sprintf("Wrong parameter : %s %s\n", $error['property'], $error['message']);
}
}
}
}
Preuve que le validateur PHP n'est pas encore tout à fait au point, le dernier exemple "passe" alors qu'il est invalide. Mais bon, l'esprit est là.
J'espère que ce billet vous a donné des idées, n'hésitez pas à les partager.
Notes
[1] désolé pour la lisibilité du code, Gandi qui héberge ce blog ne fournit pas de plugin de coloration syntaxique
Commentaires
C'est sans doute utile dans certains cas... mais pour autant ce qui est appréciable avec JSON c'est que justement ce n'est *pas* comme du XML et que l'intérêt est (était?) d'avoir un format *simple* pour des problèmes simples à résoudre (échanger des données de formats courant d'un langage à l'autre par exemple).
En plus faire des spécifications c'est bien... les implémenter correctement c'est mieux. Et quand je vois déjà comment est maltraitée la pourtant très simple spécification json-rpc 1.0 par certains frameworks... je crains que le remède json schema ne soit pire que la maladie qu'il combat (qui a dit soap sucks ?).
Bref, ma question serait: si ça fait "comme XML Schema", pourquoi ne pas utiliser XML Schema ?
Une citation pour illustrer mon propos:
"The deeper problem with SOAP is strong typing. WSDL accomplishes its magic via XML Schema and strongly typed messages. But strong typing is a bad choice for loosely coupled distributed systems. The moment you need to change anything, the type signature changes and all the clients that were built to your earlier protocol spec break. "
http://www.somebits.com/weblog/tech...
@defresnes : JSON Schema est un format simple pour valider simplement des données simples
JSON est très proche de la structure des objets que l'on manipule en programmation, objets ou tableaux essentiellement. Je le trouve donc plus aisé d'emploi, plus "naturel" pour décrire, transporter, valider ces objets. Mais c'est assez subjectif.
Et puis, il m'a fallu dix minutes pour lire la spec et commencer à jouer avec. XML Schema est plus puissant, mais demande bien plus d'investissement.
Pour ce qui est de SOAP, je suis bien d'accord que c'est trop lourd, trop rigide. Mais c'est un développeur de choisir la "force" des contraintes de schéma. La techno derrière, que ce soit JSON Schema ou XML Schema, ne change pas grand chose.
Attention, je ne remets pas en cause JSON. J'en apprécie la simplicité au quotidien, notamment pour du json-rpc.
Reste selon moi toujours le même problème avec ces normes: la qualité de leurs implémentations. Je ne parlerai pas de JSON Schema, je n'ai pas creusé ce point précis, mais prenons json-rpc 1.0: La spec est à la fois très minimale et très peu sujette à interprétation:
in: { "method": "echo", "params":
oops... dotclear n'a pas l'air d'apprécier qu'on mette du JSON dans les commentaires
Je remets mon message, sans la spec de toute façon facilement dispo:
Attention, je ne remets pas en cause JSON. J'en apprécie la simplicité au quotidien, notamment pour du json-rpc.
Reste selon moi toujours le même problème avec ces normes: la qualité de leurs implémentations. Je ne parlerai pas de JSON Schema, je n'ai pas creusé ce point précis, mais prenons json-rpc 1.0: La spec est à la fois très minimale et très peu sujette à interprétation:
dotclear a du mal
Là c'est très clair... la réponse doit contenir une clé "error", éventuellement à null. Or que fait l'implémentation d'un service json-rpc du framework zend ? et bien elle zappe tout simplement la clé s'il n'y a pas d'exceptions... causant le trouble pour un client json-rpc qui, lui, implémenterait correctement la spec. Résultat, si on a pas le choix de patcher le service, il faut ré-inventer la roue et redévelopper un client json-rpc qui tienne compte des défaillances du service. C'est vite fait mais c'est quand même dommage.
Evidemment si tout le monde suivait la spec tout irait bien... sauf que tout le monde marche dessus.
Après tu réponds à ma question en disant que XML Schema demande plus d'investissement que JSON Schema... et bien tant mieux et j'espère que ce sera toujours le cas, pourtant je me pose cette question: Combien de temps avant que JSON devienne le nouveau XML, avec tous ses travers ?
Sinon pour la coloration syntaxique de ton blog, ce truc marche très bien: http://google-code-prettify.googlec...
@+!
Merci pour les précisions sur l'implémentation de JSON-RPC dans Zend, je n'avais pas encore eu l'occasion de jouer avec, je ferai attention si je dois l'utiliser.
Et merci pour la bibliothèque JS de coloration syntaxique, elle m'a l'air de bien répondre à mon besoin, je l'intégrerai dès que j'aurai quelques minutes...
J'ai du me faire une petite classe pour palier le pbm avec ZF, je la met là à toutes fins utiles: http://www.desfrenes.com/python-jso...