Skip to content

Instantly share code, notes, and snippets.

@pjrt
Last active August 29, 2015 14:02
Show Gist options
  • Save pjrt/df3ddc633926287fe053 to your computer and use it in GitHub Desktop.
Save pjrt/df3ddc633926287fe053 to your computer and use it in GitHub Desktop.
A way of allowing backwards compatibility for Json field names using Play Json Combinators

The following shows how to use the alternative typeclass in JsResult

The following imports are assumed in all examples:

import play.api.libs.json._
import play.api.libs.functional.syntax._

Lets say we have the following case class

 case class Value(name: String, percentage: Float)
 object Value {
   implicit val valueFormat = Json.format[Value]
 }

Lets say that the Value class used to have a field called "title" instead of "name" and we want to be backwards compatible. We would like a way to parse incoming JSON with "title" instead of "name" to be correct.

Manually building Reads[Value]

When you call Json.format[Value] you are automatically building both a Reads[Value] and a Writes[Value] out of the case class. However, if you want a field to have a different name in the Json than in the class, you will have to manually build Reads and/or Writes (Format is simply the combination of these two).

Let us create a manual Reads[Value]:

case class Value(name: String, percentage: Float)
object Value {
 implicit val valueReads = 
     ((__ \ "name").read[String] and (__ \ "percentage").read[Float])(Value.apply _)

Notice the and function? What that is doing is applying the result of the first calculation (the "name" calculation) to the next calculation (the "percentage" calculation). and states that if either one of them fails, the whole thing fails (but it will keep the results of both).

There is a function or (aliased |) that will instead of failing both, it will pick (alternate) between the two the one that successed. If both successed, then it picks the first one (so order matters. Put the "new" field name first).

Let us create a Reads[Value] that will accept "name" or "title".

case class Value(name: String, percentage: Float)
object Value {
 implicit val valueReads = 
     ((__ \ "name").read[String] or (__ \ "title").read[String] and (__ \ "percentage").read[Float])(Value.apply _)

Notice the or between tne "name" and "title" calculations. We still want an and for the "percentage". Now, when you call JsValue.validate[Value], it will try to validate "name". But if it fails, it will look for "title". If that fails, then a JsError is return telling you that it couldn't find neither name or title.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment