A TypeScript application sends a Uint8Array
object through an HTTP POST
request to a Scala
Play
application.
How to convert the Uint8Array
object into a Scala
object in the Play
application?
For example, the TypeScript
code sends the following object:
{stuff: new Uint8Array([0, 1, 2])}
Inside the Scala
Play
controller:
case class StuffData(stuff: List[Byte])
implicit val reads: Reads[StuffData] = Json.reads[StuffData]
def processStuff = Action.async(parse.json[StuffData]) { request =>
val stuffData = request.body
println(stuffData)
}
This does not work... the error message from Play
is:
For request 'POST /send-stuff'
[Json validation error List((obj.key,List(JsonValidationError(List(error.expected.jsarray),WrappedArray()))))]
UPDATE:
To be able to get on the Scala side exactly the same byte array as was on the JavaScript side (i.e. a byte for a byte, in the original order), the current code in the accepted answer should be updated to something like:
implicit val stuffReader = new Reads[StuffData] {
def reads(js: JsValue): JsResult[StuffData] = {
JsSuccess(StuffData(
(js \ "stuff").as[Map[String, Int]].toList.sortBy(_._1.toInt).map(_._2.toByte)
))
}
}
Now, the reader populates a
case class StuffData(stuff: List[Byte])
with the same order as the original JavaScript Uint8Array
.
It's possible to convert all the Int
s into Byte
s without losing information, because we know that all the Int
s are in the range [0..255].
By default, Unit8Array is encoded in JSON as {"0":1,"1":2,"2":3,"3":4}
, so you can decode it in Scala as a Map or write your custom reader, that can translate this object into an array type. Or you could make changes from the other side, instead of using Uint8Array you can use an array or a custom stringify function that makes expected JSON.
In my opinion, the easiest one is writing the custom reader. Custom reader example:
implicit val stuffReader = new Reads[StuffData] {
def reads(js: JsValue): JsResult[StuffData] = {
JsSuccess(StuffData(
(js \ "stuff").as[Map[String, Int]].toList.map(_._2)
))
}
}