Search code examples
scalasprayspray-json

Return json response with subset of object properties in spray-json


I have an API class inspired by the spray scala demo here I am writing that will render a Person as JSON object in a spray route.

trait UsersApi {

  case class Person(name: String, firstName: String, age: Int)

  object MyJsonProtocol extends DefaultJsonProtocol {
    implicit val PersonFormat = jsonFormat3(Person)
  }

  import MyJsonProtocol._
  import spray.httpx.SprayJsonSupport._
  import spray.util._

  val bob = Person("Bob", "Parr", 32)

  val usersApiRouting: Route = {
    path("users") {
      get {
        complete {
          marshal(bob)
        }
      }
    }
  }
}

The problem is marshal(bob) returns JSON like this:

{
  "name": "Bob",
  "firstName": "Parr",
  "age": 32
}

Imagine I need to render the JSON without "age" like this:

{
  "name": "Bob",
  "firstName": "Parr"
}

How could this be achieved? One thought I have is does Scala have a way to make an object that is a subset of the properties of another object? Or perhaps would spray-json have some specific support for not marshalling a property that should not be added to the server response?


Solution

  • According to spray-json docs, you should provide custom jsonFormat like that:

    case class Person(name: String, firstName: String, age: Option[Int])
    
      object MyJsonProtocol extends DefaultJsonProtocol {
        implicit object PersonFormat extends RootJsonFormat[Person] {
        def write(p: Person) =JsObject(
          "name" -> JsString(p.name),
          "firstName" -> JsString(p.firstName)
        )
    
        def read(value: JsValue) = {
          value.asJsObject.getFields("name", "firstName", "age") match {
            case Seq(JsString(name), JsString(firstName), JsNumber(age)) =>
              new Person(name, firstName, Some(age.toInt))
            case Seq(JsString(name), JsString(firstName)) =>
              new Person(name, firstName, None)
            case _ => throw new DeserializationException("Person expected: " + value.asJsObject.getFields("name", "firstName", "age").toString)
          }
        }
      }
      }
    
      import MyJsonProtocol._
      import spray.httpx.SprayJsonSupport._
      import spray.util._
    
      val bob = Person("Bob", "Parr", Some(32))
    
      val bobJson = bob.toJson.toString  //bobJson: String = {"name":"Bob","firstName":"Parr"}
    
      val bobObj = bobJson.parseJson.convertTo[Person]  //bobObj: Person = Person(Bob,Parr,None)