Search code examples
scalaplay-json

Mapping a string to case classes using Scala play


Using the Scala play library I'm attempting to parse the string :

  var str = "{\"payload\": \"[{\\\"test\\\":\\\"123\\\",\\\"tester\\\":\\\"456\\\"}," +
    "{\\\"test1\\\":\\\"1234\\\",\\\"tester2\\\":\\\"4567\\\"}]\"}";

into a list of Payload classes using code below :

import play.api.libs.json._

object TestParse extends App {
  
  case class Payload(test : String , tester : String)
  object Payload {
    implicit val jsonFormat: Format[Payload] = Json.format[Payload]
  }

  var str = "{\"payload\": \"[{\\\"test\\\":\\\"123\\\",\\\"tester\\\":\\\"456\\\"}," +
    "{\\\"test1\\\":\\\"1234\\\",\\\"tester2\\\":\\\"4567\\\"}]\"}";

  println((Json.parse(str) \ "payload").as[List[Payload]])

}

build.sbt :

name := "akka-streams"

version := "0.1"

scalaVersion := "2.12.8"

lazy val akkaVersion = "2.5.19"
lazy val scalaTestVersion = "3.0.5"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % akkaVersion,
  "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion,
  "com.typesafe.akka" %% "akka-testkit" % akkaVersion,
  "org.scalatest" %% "scalatest" % scalaTestVersion
)

// https://mvnrepository.com/artifact/com.typesafe.play/play-json
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.10.0-RC6"

It fails with exception :

Exception in thread "main" play.api.libs.json.JsResultException: JsResultException(errors:List((,List(JsonValidationError(List("" is not an object),WrappedArray())))))

Is the case class structure incorrect ?

I've updated the code to :

import play.api.libs.json._

object TestParse extends App {

  import TestParse.Payload.jsonFormat
  object Payload {
    implicit val jsonFormat: Format[RootInterface] = Json.format[RootInterface]
  }
  case class Payload (
                       test: Option[String],
                       tester: Option[String]
                     )

  case class RootInterface (
                             payload: List[Payload]
                           )

  val str = """{"payload": [{"test":"123","tester":"456"},{"test1":"1234","tester2":"4567"}]}"""

  println(Json.parse(str).as[RootInterface])

}

which returns error :

No instance of play.api.libs.json.Format is available for scala.collection.immutable.List[TestParse.Payload] in the implicit scope (Hint: if declared in the same file, make sure it's declared before) implicit val jsonFormat: Format[RootInterface] = Json.format[RootInterface]


Solution

  • This performs the task but there are cleaner solutions :

    import akka.actor.ActorSystem
    import akka.stream.scaladsl.{Flow, Sink, Source}
    import org.scalatest.Assertions._
    import spray.json.{JsObject, JsonParser}
    
    import scala.concurrent.Await
    import scala.concurrent.duration.DurationInt
    
    object TestStream extends App {
      implicit val actorSystem = ActorSystem()
      val mapperFlow = Flow[JsObject].map(x => {
        x.fields.get("payload").get.toString().replace("{", "")
          .replace("}", "")
          .replace("[", "")
          .replace("]", "")
          .replace("\"", "")
          .replace("\\", "")
          .split(":").map(m => m.split(","))
          .toList
          .flatten
          .grouped(4)
          .map(m => Test(m(1), m(3).toDouble))
          .toList
      })
    
      val str = """{"payload": [{"test":"123","tester":"456"},{"test":"1234","tester":"4567"}]}"""
      case class Test(test: String, tester: Double)
    
    val graph = Source.repeat(JsonParser(str).asJsObject())
      .take(3)
      .via(mapperFlow)
      .mapConcat(identity)
      .runWith(Sink.seq)
    
      val result = Await.result(graph, 3.seconds)
    
      println(result)
      assert(result.length == 6)
      assert(result(0).test == "123")
      assert(result(0).tester == 456 )
      assert(result(1).test == "1234")
      assert(result(1).tester == 4567 )
      assert(result(2).test == "123")
      assert(result(2).tester == 456 )
      assert(result(3).test == "1234")
      assert(result(3).tester == 4567 )
    
    }
    

    Alternative, ioiomatic Scala answers are welcome.