Description of the problem:

  • I have a Scala web service which accepts JSON from a UI.
  • The user input contains nested logical objects which I want to extract cleanly. For example, I have a User data model, which in turn contains an Address data model.
  • I want to turn the incoming JSON directly into my target Scala data model but I am seeing errors like org.json4s.package MappingException: No usable value ...

Let’s walk thru a solution. Here’s our Scala data model as discussed above:

case class Address(street: String, city: String, state: String, zip: String)

// User contains a nested Address instance
case class User(id: Long, name: String, email: String, address: Address)

The UI returns the following JSON to our Scala http server:

{
  "id": 5407,
  "name": "Clementine Bauch",
  "email": "Sincere@april.biz",
  "address": {
    "street": "87473 Kulas Light",
    "city": "Gwenborough",
    "state": "CA",
    "zipcode": "92998"
  }
}

in our Scala app, we have brought in the following json4s dependencies (build.gradle format):

dependencies {
  compile "org.json4s:json4s-ext_2.12:3.5.0"
  compile "org.json4s:json4s-jackson_2.12:3.5.0"
  compile "org.json4s:json4s-native_2.12:3.5.0"
}

OK! Let’s try to parse this JSON:

case class Address(street: String, city: String, state: String, zip: String)
case class User(id: Long, name: String, email: String, address: Address)
implicit val formats = org.json4s.DefaultFormats

val jsonRequestString = s"""
     |{
     |  "id": 5407,
     |  "name": "Clementine Bauch",
     |  "email": "Sincere@april.biz",
     |  "address": {
     |    "street": "87473 Kulas Light",
     |    "city": "Gwenborough",
     |    "state": "CA",
     |    "zip": "92998"
     |  }
     |}
   """.stripMargin

val parsedJson = parse(jsonRequestString)
val user = parsedJson.extract[User]

/*
* org.json4s.package$MappingException: No usable value for address
* No usable value for zip
* Did not find value which can be converted into java.lang.String
*/

What’s happening here is that json4s does not know how to serialize or deserialize our nested case class. The solution is to implement a CustomSerializer serdes.

details: https://github.com/json4s/json4s#serializing-non-supported-types

It is pretty straightforward. We need to supply an implementation for converting from a JObject to an Address instance, and then from an Address instance back to a JObject. Here’s how that looks:

import org.json4s.{CustomSerializer, JField, JObject, JString}

class AddressSerializer extends CustomSerializer[Address](format => (
  {
    case JObject(
      JField("street", JString(street))
      :: JField("city", JString(city))
      :: JField("state", JString(state))
      :: JField("zip", JString(zip))
      :: Nil
    ) => Address(street, city, state, zip)
  },
  {
    case address: Address =>
      JObject(
        JField("street", JString(address.street))
        :: JField("city", JString(address.city))
        :: JField("state", JString(address.state))
        :: JField("zip", JString(address.zip))
        :: Nil
      )
  }
))

Now we just need to update the implicit Formats which is in scope for the serdes work:

implicit val formats = org.json4s.DefaultFormats + new AddressSerializer

val user = parsedJson.extract[User]

/*
* User(5407L, "Clementine Bauch", "Sincere@april.biz", Address("87473 Kulas Light", "Gwenborough", "CA", "92998"))
*/

Now, we are able to work with Nested Case Classes and Json using json4s