Handle multidimensional JSON with scala Play frame

2019-08-23 01:50发布

问题:

I am trying to send data from the client to the server using a JSON request. The body of the JSON request looks like this:

[
 [
  {"x":"0","y":"0","player":0},
  {"x":"0","y":"1","player":0},                      
  {"x":"0","y":"2","player":1}
 ],
 [
  {"x":"1","y":"0","player":0},
  {"x":"1","y":"1","player":2},
  {"x":"1","y":"2","player":0}
 ],
 [
  {"x":"2","y":"0","player":0},
  {"x":"2","y":"1","player":1},
  {"x":"2","y":"2","player":2}
 ]
] 

On server side I would like to transform data with Play 2 framework to Scala 2D list like this:

List(
 List(0,0,1),
 List(0,2,0),
 List(0,1,2)
)

this is 3x3 but it can be variable like 50x50 or so.

Thanks for any help.

回答1:

It might be incomplete (don't know if you want to modelize the square matrix contraint as well) but something like that could be a good start:

First here is what the controller (and model) part can define

import play.api.libs.json.Json._
import play.api.libs.json._

type PlayerT = (String, String, Int)

implicit val playerTripleReads:Reads[PlayerT] = (
  (__ \ "x").read[String] and
  (__ \ "y").read[String] and
  (__ \ "player").read[Int]
  tupled
)

def getJson = Action(parse.json) { request =>
  request.body.validate[List[List[PlayerT]]].map{
    case xs => Ok(xs.mkString("\n"))
  }.recoverTotal{
    e => BadRequest("Detected error:"+ JsError.toFlatJson(e))
  }
}

In this version, you'll get a list of list holding validated tuples of the form (String, String, Int) which has been aliased with the PlayerT type to save some typing.

As you may saw, the reader as been created "by-hand" by composing (using the and combinator) three basic blocks and the result is flattened using the tupled operator.

With this solution you're now on track to play with those tuples, but IMO the code will suffer from bad readability, because of the usage of _1, _2 and _3 along the way.

So here is a different approach (which is in fact even easier...) that tackles this problem of sane coding, this will simply defined a `case class that models your atomic data

case class Player(x:String, y:String, player:Int)

implicit val playerReads = Json.reads[Player]

def getJson = Action(parse.json) { request =>
  request.body.validate[List[List[Player]]].map{
    case xs => Ok(xs.mkString("\n"))
  }.recoverTotal{
    e => BadRequest("Detected error:"+ JsError.toFlatJson(e))
  }
}

Note that, the reader will always follow further changes in your data representation, that is the case class's fields thanks to the use of the implicit creation of the reader at compile time.

Now, you'll be able to use x, y and player fields rather than _1, _2 and _3.