8

I'm new to the Spray-Json API and I'm trying to parse a Json response from the Docker REST API.

There is a clean example of the usage of Spray-Json to parse this Google Map Json response :

{
   "results" : [
      {
         "elevation" : 8815.7158203125,
         "location" : {
            "lat" : 27.988056,
            "lng" : 86.92527800000001
         },
         "resolution" : 152.7032318115234
      }
   ],
   "status" : "OK"
}

In the above example the outermost level is an Object. However, I need to directly parse a Json response whose outermost level is an Array composed of containers information as shown below :

[
     {
       "Id": "8dfafdbc3a40",
       "Image": "base:latest",
       "Command": "echo 1",
       "Created": 1367854155,
       "Status": "Exit 0",
       "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
       "SizeRw":12288,
       "SizeRootFs":0
     },
     { ... },
     { ... }
]

Here is a code that I adapted from the Google map example :

package main

import ...

case class Container(id: String, image: String, command: String, created: Long, status: String, ports: List[Port], sizeRW: Long, sizeRootFs: Long)
case class Port(privatePort: Long, publicPort: Long, portType: String)
case class DockerApiResult[T](results: List[T])

object ContainerListJsonProtocol extends DefaultJsonProtocol {
  implicit val portFormat = jsonFormat3(Port)
  implicit val containerFormat = jsonFormat8(Container)
  implicit def dockerApiResultFormat[T :JsonFormat] = jsonFormat1(DockerApiResult.apply[T])
}

object Main extends App {

  implicit val system = ActorSystem("simple-spray-client")
  import system.dispatcher // execution context for futures below
  val log = Logging(system, getClass)

  log.info("Requesting containers info...")

  import ContainerListJsonProtocol._
  import SprayJsonSupport._
  val pipeline = sendReceive ~> unmarshal[DockerApiResult[Container]]

  val responseFuture = pipeline {
    Get("http://<ip-address>:4243/containers/json")
  }

  responseFuture onComplete {
    case Success(DockerApiResult(Container(_,_,_,_,_,_,_,_) :: _)) =>
      log.info("Id of the found image: {} ")
      shutdown()

    case Success(somethingUnexpected) =>
      log.warning("The Docker API call was successful but returned something unexpected: '{}'.", somethingUnexpected)
      shutdown()

    case Failure(error) =>
      log.error(error, "Couldn't get containers information")
      shutdown()
  }

  def shutdown(): Unit = {
    IO(Http).ask(Http.CloseAll)(1.second).await
    system.shutdown()
  }
}

And below is the exception I get (Object expected) :

spray.httpx.PipelineException: MalformedContent(Object expected,Some(spray.json.DeserializationException: Object expected))

I certainly miss something obvious but How to parse a Json Array using Spray-Json?

Also, is there a simple way to do this without having to deal with custom JsonFormat or RootJsonFormat?

1
  • Personally, I would switch to Lift-json :) Commented Dec 11, 2013 at 15:48

2 Answers 2

16

By doing unmarshal[DockerApiResult[Container]], you're telling spray-json that you expect the format to be a json object of the form:

{ results: [...] }

since case class DockerApiResult[T](results: List[T]) is defined as an object with a single results field containing a list.

Instead you need to do:

unmarshal[List[Container]]

and then operate on the resulting list directly (or wrap it in a DockerApiResult after it has been parsed by spray-json).

If you want spray-json to unmarshal directly into a DockerApiResult, you can write a JsonFormat with something like:

implicit object DockerApiResultFormat extends RootJsonFormat[DockerApiResult] {
  def read(value: JsValue) = DockerApiResult(value.convertTo[List[Container]])
  def write(obj: DockerApiResult) = obj.results.toJson
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! It worked. I actually tried with unmarshal[List[Container]] but I got confused with the Port attribute that was null and I had to wrap it with Option. Anyway great answer, exactly what I was looking for ;)
13

fought with this a little and found a way to convert to JsArray from a json parsed string using spray:

import spray.json._   //parseJson
val kkkk =
  """
    |[{"a": "1"}, {"b": "2"}]
  """.stripMargin.parseJson.asInstanceOf[JsArray]

1 Comment

Does this maintain the order of elements?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.