Beim Herumspielen mit dem Play! Framework bin ich heute über die etwas klobige JSON-Integration zum Lesen des Body einer HTTP-Anfrage gestolpert. Der empfohlene Ansatz, typsicheres JSON zu verwenden und den Baum zu durchsuchen, während Sie Ihr benutzerdefiniertes Objekt erstellen oder ein Format verwenden, scheint für die meisten Standardsituationen, die von den Standardbibliotheken Jerkson und Jackson (in Play! enthalten) anmutig gehandhabt werden, ziemlich umständlich zu sein. In diesem Blog beschreibe ich einen Ansatz, der einen benutzerdefinierten BodyParser verwendet, um eine einfachere Lösung zu finden.
Der empfohlene Ansatz zum Schreiben einer Aktion, die JSON verarbeitet, besteht darin, ein Format für Ihre Fallklasse zu schreiben, etwa so:
[scala]
case class Person (id: Long, firstName: String, lastName: String)
object Person {
def all():List[Person] = ...
def create(p : Person):Long = ...
import play.api.libs.json.
implicit object PersonFormat extends Format[Person] {
def reads(json: JsValue) = Person(
(json "id").as[Long],
(json "vorname").as[String],
(json "nachname").as[String])
def writes(p: Person) = JsObject(Seq(
"id" -> JsNumber(p.id),
"vorname" -> JsString(p.vorname),
"nachname" -> JsString(p.nachname)))
}
}
[/scala]
Der JSON-Parser kann nun das Format in den Aktionen wie folgt verwenden:
[scala]
import play.api.mvc.
import play.api.libs.json.Json.
object People extends Controller {
def list = Action {
Ok(toJson(Person.all()))
}
def save = Action(parse.json) { implicit request =>
var id = Person.create(request.body.as[Person])
Ok(toJson(Map("id" -> id)))
[/scala] }
}
In Zeile 6 wird das Format implizit zur Konvertierung der Person-Objekte in JSON verwendet. In Zeile 10 wird das Standard-JSON-Parsing in der Aktion verwendet. In Zeile 11 wird der geparste Body unter Verwendung des Formats wieder implizit in eine Person umgewandelt und an den Rest der Anwendung übergeben.Das sieht ganz gut aus, aber wenn Sie eine große Anzahl von Klassen mit vielen Feldern haben, wird das Schreiben aller Formate ziemlich mühsam. Vor allem, wenn man bedenkt, dass die Jerkson-Bibliothek, die hinter all dem steht, bereits einfache Objekt-zu-JSON-Mappings unterstützt, die in den meisten Situationen ausreichen.Für Standardsituationen wäre es einfacher, überhaupt kein Format schreiben zu müssen. Dies kann durch die Verwendung eines generischen Body-Parsers wie diesem erreicht werden:
[scala]
import com.codahale.jerkson.Json
import play.api.Play
import play.api.mvc.
import BodyParsers.parse. DEFAULT_MAX_TEXTLENGTH
import play.api.libs.iteratee.
import play.api.libs.iteratee.Input.
class JsonObjectParser[A : Manifest] extends BodyParser[A] {
def apply(request: RequestHeader): Iteratee[Array[Byte], Either[Ergebnis, A]] = {
Traversable. takeUpTo Array[Byte] .apply(Iteratee.consume[Array[Byte]]().map { bytes =>
scala.util.control.Exception.allCatch[A].either {
Json.parse[A](new String(bytes, request.charset.getOrElse("utf-8")))
}.left.map { e =>
(Play.maybeApplication.map(.global.onBadRequest(request, "Invalid Json")).getOrElse(Results.BadRequest), bytes)
}
}).flatMap(Iteratee.eofOrElse(Results.EntityTooLarge))
.flatMap {
case Left(b) => Done(Left(b), Empty)
case Right(it) => it.flatMap {
case Left((r, in)) => Done(Left(r), El(in))
case Right(a) => Done(Right(a), Empty)
}
}
}
}
[/scala]
(Das meiste davon wurde vom JSON-Parser in der BodyParsers-Eigenschaft des Play! Frameworks kopiert).
Dieser generische Objektparser verwendet jerkson direkt, um den Body in ein Scala-Objekt zu konvertieren.
Jetzt kann der Anwendungscode viel einfacher sein:
[scala]
case class Person (id: Long, firstName: String, lastName: String)
object Person {
def all():List[Person] = ...
def create(p : Person):Long = ...
}
import play.api.mvc._
import com.codahale.jerkson.Json
object People extends Controller {
val personParser = new JsonObjectParser[Person]()
def list = Action {
Ok(Json.generate(Person.all()))
}
def save = Action(personParser) { implicit request =>
var id = Person.create(request.body)
Ok(Json.generate(Map("id" -> id)))
}
}
[/scala]
Anstelle der Play! Abstraktion verwenden wir jetzt direkt Jerkson, um die Antworten zu schreiben. In Zeile 15 wird der generische Parser mit einem konkreten Typ instanziiert. In Zeile 21 wird er mit der Aktion verwendet.
Die Arbeit mit JSON in einer großen Play!-Anwendung wird mit diesem Ansatz viel einfacher!