Publié par

Il y a 5 années -

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

Publié par

Publié par Nicolas Jozwiak

Nicolas est delivery manager disposant de 12 ans d’expérience en conception et développement. Son parcours chez un éditeur avant son entrée chez Xebia lui a notamment permis de développer de solides compétences dans le domaine de la qualité et de l’industrialisation (tests, intégration continue, gestion de configuration, contrôle qualité). Bénéficiant d’une expérience très solide de mise en place des méthodes agiles et d’accompagnement d’équipes sur le terrain, il s’attache à mettre à profit quotidiennement son expérience qui est reconnue pour son approche pragmatique, proactive et pédagogique.

Commentaire

6 réponses pour " Validez votre Json avec Play / Scala "

  1. Publié par , Il y a 5 années

    Une signature intéressante sur le JsResult en plus de map est fold:

    rs.request.body.validate[User].fold( invalid => ???, valid => ???)

  2. Publié par , Il y a 5 années

    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
    }

  3. Publié par , Il y a 5 années

    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(…)

  4. Publié par , Il y a 5 années

    Bonjour,

    Merci pour vos retours et améliorations !

    Nicolas (Xebia)

  5. Publié par , Il y a 4 années

    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

  6. Publié par , Il y a 4 années

    Bonjour,

    Cet article est vraiment intéressant. Est-il possible de faire la même chose avec Play Java ?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.