0

I've got a List which holds some Personalization-objects. The latter is defined like this:

  sealed case class Personalization(firstname: String, lastname: String, keycardId: String)

I need to map this list to a Json-Array structure which has to look like this:

"personalization": [
    {
      "id": "firstname",
      "value": "John"
    },
    {
      "id": "lastname",
      "value": "Doe"
    }...

I am struggling with the part of mapping the field information to id/value pairs. Normally, I would create a play.api.libs.json.Format out of the Personalization class and let it map automatically -> Json.format[Personalization] - but this time, I need to create an array where an entry can hold n attributes.

Therefore I am asking for advice, if there is a possibility to use the Scala Play-Framework? Any input is much appreciated, thank you!

2 Answers 2

1

Writing as such JSON representation is not quite complex, using Writes.transform.

import play.api.libs.json._

case class Personalization(firstname: String, lastname: String, keycardId: String) // No need to seal a case class

implicit def writes: Writes[Personalization] = {
  val tx: JsValue => JsValue = {
    case JsObject(fields) => Json.toJson(fields.map {
      case (k, v) => Json.obj("id" -> k, "value" -> v)
    })

    case jsUnexpected => jsUnexpected // doesn't happen with OWrites
  }

  Json.writes[Personalization].transform(tx)
}

Which can be tested as bellow.

val personalization = Personalization(
  firstname = "First",
  lastname = "Last",
  keycardId = "Foo")

val jsonRepr = Json.toJson(personalization)
// => [{"id":"firstname","value":"First"},{"id":"lastname","value":"Last"},{"id":"keycardId","value":"Foo"}]

Reading is a little bit tricky:

implicit def reads: Reads[Personalization] = {
  type Field = (String, Json.JsValueWrapper)

  val fieldReads = Reads.seq(Reads[Field] { js =>
    for {
      id <- (js \ "id").validate[String]
      v <- (js \ "value").validate[JsValue]
    } yield id -> v
  })

  val underlying = Json.reads[Personalization]

  Reads[Personalization] { js =>
    js.validate(fieldReads).flatMap { fields =>
      Json.obj(fields: _*).validate(underlying)
    }
  }
}

Which can be tested as bellow.

Json.parse("""[
  {"id":"firstname","value":"First"},
  {"id":"lastname","value":"Last"},
  {"id":"keycardId","value":"Foo"}
]""").validate[Personalization]
// => JsSuccess(Personalization(First,Last,Foo),)

Note that is approach can be used for any case class format.

Sign up to request clarification or add additional context in comments.

Comments

0

Probably it is possible to do it a more elegant way I did, but you can use the following snippet:

case class Field(id: String, value: String)

object Field {
  implicit val fieldFormatter: Format[Field] = Json.format[Field]
}

sealed case class Personalization(firstname: String, lastname: String, keycardId: String)

object Personalization {

  implicit val personalizationFormatter: Format[Personalization] = new Format[Personalization] {
    override def reads(json:  JsValue): JsResult[Personalization] =
      Try {
        val data = (json \ "personalization").as[JsValue]
        data match {
          case JsArray(value) =>
            val fields = value.map(_.as[Field]).map(f => f.id -> f.value).toMap
            val firstname = fields.getOrElse("firstname", throw new IllegalArgumentException("Mandatory field firstname is absent."))
            val lastname = fields.getOrElse("lastname", throw new IllegalArgumentException("Mandatory field lastname is absent."))
            val keycardId = fields.getOrElse("keycardId", throw new IllegalArgumentException("Mandatory field keycardId is absent."))
            Personalization(firstname, lastname, keycardId)
          case _ => throw new IllegalArgumentException("Incorrect json format for Personalization.")
        }
      }.toEither.fold(e => JsError(e.getMessage), JsSuccess(_))

    override def writes(o:  Personalization): JsValue = {
      val fields = List(Field("firstname", o.firstname), Field("lastname", o.lastname), Field("keycardId", o.keycardId))
      JsObject(List("personalization" -> Json.toJson(fields)))
    }
  }
}

It converts {"personalization":[{"id":"firstname","value":"John"},{"id":"lastname","value":"Doe"},{"id":"keycardId","value":"1234"}]} to Personalization(John,Doe,1234) and vice versa

2 Comments

Throwing exception, even in Try is not recommended, specially when JsResult could be used.
.validate should be preferred to use .as

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.