javascripttypescriptscalaplayframeworkuint8array

Convert JavaScript Uint8Array object into Scala Object


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 Ints into Bytes without losing information, because we know that all the Ints are in the range [0..255].


Solution

  • 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)
          ))
        }
      }