Il y a 9 ans -
Temps de lecture 3 minutes
Validez votre Json avec Play / Scala
Play est un framework permettant de démarrer et développer rapidement des applications Web. Ce dernier offre un large éventail d’outils. Parmi eux, nous retrouvons la validation de règles métiers.
Dans cet article, nous vous proposons de mettre en place le mécanisme de validation de Json avec Play / Scala.
Application
Nous allons utiliser une application offrant une API de gestion d’utilisateurs, et plus précisément l’ajout d’un utilisateur.
Voici la définition d’un utilisateur dans notre application :
case class User(id: Option[Long], email: String, firstName: String, lastName: String, dateAccess:LocalDate)
Dans la configuration de Play nous définissons la route suivante :
POST /users controllers.MainController.newUser
Ainsi que la ressource associée dans notre Controller :
import play.api.mvc.Controller import play.api.libs.json._ import service.UsersService import model.User import play.api.libs.functional.syntax._ import play.api.libs.json.Reads._ import play.api.data.validation.ValidationError object MainController extends Controller { def newUser = DBAction(parse.json) { implicit rs => rs.request.body.validate[User].map { case user => UsersService save User(None, user.email, user.firstName, user.lastName, user.dateAccess) Created("User Created") }.recoverTotal { e => NotFound("Detected error:" + JsError.toFlatJson(e)) } } }
Les connaisseurs remarquerons que nous utilisons la bibliothèque Slick pour la persistance de nos données. Nous en parlerons dans un prochain billet.
La ligne qui nous intéresse est la suivante :
rs.request.body.validate[User].map
La méthode validate permet d’appliquer des règles de validation sur notre objet Json. Mais comment les définir ?
Validation
En regardant la signature de cette méthode, nous voyons :
def validate[T](implicit rds: Reads[T]): JsResult[T]
Cela signifie que nous devons définir un implicit pour notre User. Une manière simple de le déclarer est la suivante :
implicit val userRead = Json.reads[User]
Or nous remarquons qu’aucune règle de validation n’est présente. Nous devons enrichir notre implicit :
implicit val userRead: Reads[User] = {( (__ \ "id").readNullable[Long] and (__ \ "email").read(email keepAnd minLength[String](5)) and (__ \ "firstName").read(minLength[String](2) andKeep maxLength[String](30)) and (__ \ "lastName").read[String] and (__ \ "dateAccess").read(jodaLocalDateReads("yyyyMMdd")) )(User) }
La syntaxe se définit sous forme de path Json. La syntaxe __ (2 underscores) est un alias pour JsPath.
Regardons plus en détail la validation de l’email : email keepAnd minLength[String](5)
- email : valide que le champ contient une chaîne de caractères et le format du mail
- minLength : un minimum de 5 caractères est requis
- keepAnd : permet de composer les validateurs et de retourner le type Read[String] dans notre cas
Ainsi lors de l’appel à notre ressource avec le Json suivant :
{ "email": "email1@gmail.com", "firstName": "firstName1", "lastName": "lastName1", "dateAccess": "03-03-2014" }
Nous aurons l’erreur de validation suivante :
Detected error:{"obj.dateAccess":[{"msg":"error.expected.jodadate.format","args":["yyyyMMdd"]}]}
Définir son validateur
Il est bien entendu possible de définir son propre validateur. Nous allons ajouter un validateur sur le champ lastName :
def lastNameReads(implicit r: Reads[String]): Reads[String] = r.filter(ValidationError("error.lastName.numbers"))(_.matches("\S+\d{4}$"))
Cela signifie que ce champ doit se terminer par 4 chiffres, auquel cas le message d’erreur « error.lastName.numbers » sera renvoyé.
Nous n’avons plus qu’à appeler cette fonction dans notre implicit:
implicit val userRead: Reads[User] = {( (__ \ "id").readNullable[Long] and (__ \ "email").read(email keepAnd minLength[String](5)) and (__ \ "firstName").read(minLength[String](2) andKeep maxLength[String](30)) and (__ \ "lastName").read(lastNameReads) and (__ \ "dateAccess").read(jodaLocalDateReads("yyyyMMdd")) )(User) }
A travers cet article, nous avons vu la simplicité que Play / Scala nous offre pour valider du Json. Enjoy !
Lien utile : http://www.playframework.com/documentation/2.2.2/ScalaJsonCombinators
Commentaire
6 réponses pour " Validez votre Json avec Play / Scala "
Published by Jean , Il y a 9 ans
Une signature intéressante sur le JsResult en plus de map est fold:
rs.request.body.validate[User].fold( invalid => ???, valid => ???)
Published by Julien Tournay , Il y a 9 ans
Hello Nicolas,
Sympa l’article, quelques remarques cependant sur le controller.
– Dans le map, le fonction que tu passe étant totale, le case n’est pas nécessaire.
– Dans le map, tu peux utiliser copy plutôt que de tout recopier: UsersService save user.copy(id = None)
– Retourner un NotFound en cas d’erreur n’est pas top. Un petit BadRequest aurait plus de sens.
– J’aurais plutôt renvoyé le Json de l’erreur directement afin de laisser le client la parser. Ajouter « Detected error: » n’apporte rien ici.
– plutôt que de faire map + recover, utilises juste fold. C’est plus idiomatique
– Play propose un bodyparser pour json que prend directement le read en paramètre: def newUser = DBAction(parse.json[User]) { … }
– Le message « User Created » n’apporte rien, autant ne pas le renvoyer. Tu pourrais soit ne pas retourner de body, ou renvoyer le User créé en Json.
Au final j’aurais écrit un truc du genre:
def newUser = DBAction(parse.json[User]) { implicit rs =>
val user = rs.request.body
UsersService save user.copy(id = None)
Created
}
Published by PAscal Voitot , Il y a 9 ans
Hey,
Bonne initiative!
En plus des précédents commentaires, pourrais-tu corriger les choses suivantes?
– « La syntaxe __ (2 underscores) est un alias pour JsPath. » : c’est surtout un alias pour dire « je cherche dans le root de mon Json »…
– keepAnd/andKeep : ton explication ne précise pas ce que c’est… ça fait un AND entre 2 validateurs (de types potentiellement totalement différents) en ne gardant qu’un côté de la validation (gauche pour keepAnd & droite pour andKeep)
– def lastNameReads(implicit r: Reads[String])… le reads implicit ne sert pas vraiment: soit tu passes le reads explicitement, soit tu crées un Reads que tu combinerais: def lastNameReads = Reads.of[String].filter(…)
Published by Nicolas Jozwiak , Il y a 9 ans
Bonjour,
Merci pour vos retours et améliorations !
Nicolas (Xebia)
Published by canada goose snow mantra parka men's , Il y a 9 ans
My programmer is trying to convince me to move to .net from PHP. I have always disliked the idea because of the costs. But he’s tryiong none the less. I’ve been using WordPress on several websites for about a year and am anxious about switching to another platform. I have heard great things about blogengine.net. Is there a way I can transfer all my wordpress content into it? Any kind of help would be greatly appreciated!
canada goose snow mantra parka men’s http://www.snsyc.ca/js/canada-goose-Snow-Mantra/y9canada-goose-snow-mantra-parka-men-s-@q3b49bh.asp
Published by 0xBAADF00D , Il y a 9 ans
Bonjour,
Cet article est vraiment intéressant. Est-il possible de faire la même chose avec Play Java ?