Skip to content

Instantly share code, notes, and snippets.

@ScalaWilliam
Last active February 16, 2017 12:27
Show Gist options
  • Select an option

  • Save ScalaWilliam/3fe1344ba32d6c8b067b5d4c96448fdf to your computer and use it in GitHub Desktop.

Select an option

Save ScalaWilliam/3fe1344ba32d6c8b067b5d4c96448fdf to your computer and use it in GitHub Desktop.
Example of how to carry the JSON object without having to spell out the case class in Scala

You don't need to spell out the whole case class when you're doing transformations.

If your data model is changing quite a lot except for a few key parts that your application is interested in - it shouldn't even be aware of the other stuff.

Here we use play-json format composition to read the whole JsObject into one field of the case class - and write it back out.

This way, you can change the schema mercilessly without tightly coupling your application to the schema.

Same approach works in Circe JSON too. Now, if we could just add an annotation to a field where we want to dump JSON data into, that's be eery very cool.

So Scala can act like a dynamic language for you, you don't have to compromise and go all-static and type everything out.

import org.scalatest.FunSuite
import org.scalatest.Matchers._
class JsonCarryTest extends FunSuite {
test("Transforms as expected") {
val json = """{"id":"one","something":"a","other":1}"""
val result = Json.toJson {
Json
.fromJson[CarrySample](Json.parse(json)).get
.copy(something = "not a")
}
val expectedJson = """{"id":"one","something":"not a","other":1}"""
result.toString() shouldEqual expectedJson
}
}
import play.api.libs.json._
case class CarrySample(id: String, something: String, jsObject: JsObject)
object CarrySample {
implicit val reads: Reads[CarrySample] =
Json
.reads[CarrySample]
.compose(JsonCarrier.readObjectInto("jsObject"))
implicit val writes: Writes[CarrySample] = {
Json
.writes[CarrySample]
.transform(JsonCarrier.writeObjectFrom("jsObject"))
}
}
import play.api.libs.json._
object JsonCarrier {
def readObjectInto(fieldName: String): Reads[JsObject] = {
Reads.JsObjectReads.map { obj =>
obj + (fieldName -> obj)
}
}
def writeObjectFrom(fieldName: String): (JsObject) => JsObject = {
obj: JsObject =>
(obj \ fieldName).asOpt[JsObject] match {
case Some(jsObj) => jsObj ++ (obj - fieldName)
case None => obj
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment