JSON values to typed Id:s in Scala & Play

I recently published a post about how to deal with JSON objects using Scala’s case classes and Play Framework. To keep up with the theme here is another post about the same topic, but this time it’s specifically about types.

JSON format does have types, but as they are not visible in the actual content and how they are mapped to data types at the receiving end depends a lot on developers first look at the incoming data. Majority of content seems to be in String format and that seems like a safe choice, after all you can fairly safely represent a number as a String as long as you don’t process the data in any meaningful form. You would not be so lucky doing it the other way around.

This logic seems pretty valid especially if you are not certain about the format of the data. Maybe the value just happened to be a number this time, but it might include a letter next time and then using anything but String would result in a runtime exception and we definitely don’t want that. Going all-in with String does have some unfortunate side effects and some problems might be creeping into your code.

def doIt(someId: String, someOtherId: String, foo: String, bar: String)

It’s really easy to mix up the parameter order when you have methods like his and what’s the point of having static typing if you deal mostly with strings anyway. In the worst case it will “kind of” work, but the results are wrong. Moreover code like this is a pain to refactor.

So instead of a mess like this wouldn’t it be nice to deal with properly typed values instead? (From here on I concentrate more on id -values, if you need to pass on many values you probably have other design flaws as well)

def doIt(someId: SomeId, someOtherId: SomeOtherId, foo: String, bar: String)

Now we have also regained the ability to trust the developer’s best friend – the compiler. Of course there is nothing new or fancy about wrapping values to classes, but what turned out to be tricky was to maintain the handy JSON-parsing that comes with Play Framework and case classes without too much boilerplate. So, how do we actually achieve this?

First solution: Implicit conversion to typed Id -classes

Well first of all we implemented a proper base trait representing any typed id case class which we will declare later on.

trait BaseId[V] {  val value: V  }

Based on that basic trait we can then implement another trait for every value type we want to support.

trait StringBaseId extends BaseId[String]
trait NumberBaseId extends BaseId[BigDecimal]

What we now need is an implicit conversion of the JavaScript’s primitive type to an instance of a typed id implementation. We do this using an implicit class for every primitive type we support. For example String -based ids we implement like this:

implicit class StringTypedIdFormat[I <: BaseId[String]](factory: Factory[String, I]) 
    extends Format[I] {
  def reads(json: JsValue): JsResult[I] = json match {
    case JsString(value) => JsSuccess(factory(value))
    case _ => JsError(s"Unexpected JSON value $json")
  }
  def writes(id: I): JsValue = JsString(id.value)
}

The provided factory will be used to instantiate a concrete implementation based on a String. The type factory is declared as follows:

type Factory[V, I <: BaseId[V]] = V => I

In the next step we declare concrete id -classes for each id type we need. Staying consistent to our requested method signature of the previous example we write:

case class SomeId(value: String) extends StringBaseId
case class SomeOtherId(value: BigDecimal) extends NumberBaseId

Both case classes extend from our base corresponding base traits. The case class representing the JSON’s would look like this:

case class SomeObject(id:SomeId, name:String)
case class SomeOtherObject(id:SomeOtherId, name:String, value:Number)

To get an implicit conversion between JSON and those two case classes we must provide implicit read and write -functions or JsonCombinator formats like (https://www.playframework.com/documentation/2.3.x/ScalaJsonCombinators)

implicit val someObjectFormat: Format[SomeObject] = 
  Json.format[SomeObject]
implicit val someOtherObjectFormat: Format[SomeOtherObject] = 
  Json.format[SomeOtherObject]

This format wouldn’t yet work because we still need an implicit conversion between JSON and the typed id case classes. We can finally provide those format using the helper classes previously declare. For the two ids we declare:

implicit val someIdFormat: Format[SomeId] = 
  new StringTypedIdFormat[SomeId](SomeId.apply _)
implicit val someOtherIdFormat: Format[SomeOtherId] = 
  new NumberTypedIdFormat[SomeOtherId](SomeOtherId.apply _)

The default apply method of the id case classes can be used directly as the declared factory method to generate the concrete instance of the case class. We can then implicitly convert between JSON primitive type id values and our typed id case classes in Scala.

A simple test to demonstrate the conversion:

val someObjectAsJson: JsValue = Json.parse("""
  {
    "id":"111",
    "name": "someName"
  }
""")

"Parsing generic id object" should {
   "SomeId will be parsed correctly" in {
     val test = someObjectAsJson.as[SomeObject]
     test.id === SomeId("111", “someName”)
   }
}

We can further simplify the id format declaration by adding an additional method to the Scala’s Json object:

object TypedId {

  //implicit convertion to extended json object
  implicit def fromJson(json: Json.type) = TypedId

  //extended format function
  def idformat[I <: StringBaseId](fact: Factory[String, I]) = 
    new StringTypedIdFormat[I](fact)
  def idformat[I <: NumberBaseId](fact: Factory[BigDecimal, I]) = 
    new NumberTypedIdFormat[I](fact)
}

Now we can replace the format declaration with the following simpler version:

implicit val someIdFormat: Format[SomeId] = 
  Json.idformat[SomeId](SomeId.apply _)
implicit val someOtherIdFormat: Format[SomeOtherId] = 
  Json.idformat[SomeOtherId](SomeOtherId.apply _)

We still need to declare the factory method because we can’t instantiate a typed class at runtime.

We are not completely happy with this solution because with the current solution we would have to declare a concrete case class as well as an implicit format for every id class we create. We are looking for a more generic way to declare such id’s and it could look something like this:

def doIt(someId: StringId[SomeObject], someOtherId: NumberId[SomeOtherObject], foo: String, bar: String)

Bye the way:
All the base implementations can be found on GitHub.

Mike Toggweiler, a partner @ Tegonal co-authored this post.

Advertisements

Posted on 13/11/2014, in Scala, Software and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: