2017-09-10 1 views
0

J'ai deux classes qui héritent d'un parent commun. Je voudrais avoir un lecteur JSON commun dans le parent qui retournera un enfant approprié basé sur JSON fourni. Il est plus facile de l'expliquer avec un exemple d'extrait ci-dessous;Condition JSON lit dans scala

import play.api.libs.json.{JsPath, JsonValidationError, Reads} 

sealed abstract class Animal(sound: String) 

case class Goat(hooves: String) extends Animal("meh") 

case class Cat(needsMilk: Boolean) extends Animal("meow") 

val json ="""{"type": "goat", "hooves": "All good for climbing trees"}""" 

object Animal { 
    val isSupportedAnimal: Reads[String] = 
    Reads.StringReads.filter(JsonValidationError("Unsupported animal"))(str => { 
     List("goat", "cat").contains(str) 
    }) 

    val animalReads: Reads[Animal] = ((JsPath \ "type").read[String](isSupportedAnimal) and 
    //if animal is cat, use the cat specific reads and return a cat object 
    //if animal is goat, use goat specific reads and return a goat 
    )() 
} 

Compte tenu de la json dans l'extrait, je voudrais l'avoir Goat objet parce que le type spécifié est goat. Je suis novice en matière de scala, donc je pourrais aborder le problème de manière erronée. Les suggestions sont les bienvenues.

Répondre

1

Utilisez un Map:

sealed abstract class Animal(val sound: String) // You probably want a val here, btw 
final case class Goat(hooves: String) extends Animal("meh") 
final case class Cat(needsMilk: Boolean) extends Animal("meow") 

object Animal { 
    val readers: Map[String, Reads[_ <: Animal]] = Map(
    "goat" -> implicitly[Reads[Goat]], 
    "cat" -> implicitly[Reads[Cat]], 
    // Sidenote: Trailing commas ^make future modification easy 
) 
    // Bonus: Set[String] <: String => Boolean, so you get isSupportedAnimal for free 
    // val isSupportedAnimal: String => Boolean = readers.keys 

    implicit val animalReads: Reads[Animal] = new Reads[Animal] { 
    def reads(s: JsValue): JsResult[Animal] = { 
     val tpe = (s \ "type").as[String] 
     val read = readers.get(tpe) 
     read.map(_.reads(s)).getOrElse(JsError(s"Unsupported animal: $tpe")) 
    } 
    } 
} 

Si vous préférez ne pas avoir ce passe-partout, vous pouvez regarder dans this library (qui utilise sans forme).

+0

Merci. Je vois la lumière dans cette réponse. J'essaie de compiler votre extrait pour le moment. J'apprécierais si vous pouvez mettre quelque chose qui compile mais je vois votre point. – ivanorone

0

Vous pouvez essayer de faire un lecteur personnalisé comme celui-ci:

implicit val animalReads = new Reads[Animal] { 
    def reads(js: JsValue): Animal = { 
     (js \ "type").as[String] match { 
     case "cat" => Cat((js \ "needsMilk").as[Boolean]) 
     case "goat" => Goat((js \ "hooves").as[String]) 
     case _ => throw new JsonValidationError("Unsupported animal") 
     } 
    } 
    } 
+0

Merci pour la suggestion. Cela fonctionnerait-il avec la validation automatique que vous obtenez quand vous faites un 'Reads [Animal]' Supposons que vous avez une action de contrôleur en dessous de 'def saveAnimal = Action (validateJson [Animal]) {request: Request [Animal] => ' – ivanorone

+0

@ivanorone Dans ce cas, j'ai mis à jour ma réponse. – inafalcao

+0

Merci d'avoir pris le temps de travailler à travers cela. J'ai accepté @HTNW parce qu'avec cela je peux enchaîner d'autres lecteurs dans les lecteurs 'Goat' et' Cat'. – ivanorone