Scala 2.10 + Json serialization and deserialization

41,056

Solution 1

Jackson is a Java library to process JSON fast. The Jerkson project wraps Jackson, but appears to be abandoned. I've switched to Jackson's Scala Module for serialization and deserialization to native Scala data structures.

To get it, include the following in your build.sbt:

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.1.3",
   ...
)

Then your examples will work verbatim with the following Jackson wrapper (I extracted it from jackson-module-scala test files):

import java.lang.reflect.{Type, ParameterizedType}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.`type`.TypeReference;

object JacksonWrapper {
  val mapper = new ObjectMapper()
  mapper.registerModule(DefaultScalaModule)
  
  def serialize(value: Any): String = {
    import java.io.StringWriter
    val writer = new StringWriter()
    mapper.writeValue(writer, value)
    writer.toString
  }

  def deserialize[T: Manifest](value: String) : T =
    mapper.readValue(value, typeReference[T])

  private [this] def typeReference[T: Manifest] = new TypeReference[T] {
    override def getType = typeFromManifest(manifest[T])
  }

  private [this] def typeFromManifest(m: Manifest[_]): Type = {
    if (m.typeArguments.isEmpty) { m.runtimeClass }
    else new ParameterizedType {
      def getRawType = m.runtimeClass
      def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
      def getOwnerType = null
    }
  }
}

Other Scala 2.10 JSON options include Twitter's scala-json based on the Programming Scala book--it's simple, at the cost of performance. There is also spray-json, which uses parboiled for parsing. Finally, Play's JSON handling looks nice, but it does not easily decouple from the Play project.

Solution 2

Mentioning json4s that wraps jackson, lift-json or its own native implementation as a long term solution:

Solution 3

I can heartily recommend argonaut for json support in scala. All you need to configure it to serialize your Customer object is one line:

implicit lazy val CodecCustomer: CodecJson[Customer] =
casecodec6(Customer.apply, Customer.unapply)("id","name","address","city","state","user_id")

That will pimp your class to give it an .asJson method which turns it into a string. It will also pimp the string class to give it a method .decodeOption[List[Customer]] to parse strings. It handles the options in your class fine. Here is a working class with a passing test and a running main method which you can drop into a git clone of argonaut to see it all working fine:

package argonaut.example

import org.specs2.{ScalaCheck, Specification}
import argonaut.CodecJson
import argonaut.Argonaut._

case class Customer(id: Int, name: String, address: Option[String],
                    city: Option[String], state: Option[String], user_id: Int)

class CustomerExample extends Specification with ScalaCheck {

  import CustomerExample.CodecCustomer
  import CustomerExample.customers

  def is = "Stackoverflow question 12591457 example" ^
    "round trip customers to and from json strings " ! {
      customers.asJson.as[List[Customer]].toOption must beSome(customers)
    }
}

object CustomerExample {

  implicit lazy val CodecCustomer: CodecJson[Customer] =
    casecodec6(Customer.apply, Customer.unapply)("id","name","address","city","state","user_id")

  val customers = List(
    Customer(1,"one",Some("one street"),Some("one city"),Some("one state"),1)
    , Customer(2,"two",None,Some("two city"),Some("two state"),2)
    , Customer(3,"three",Some("three address"),None,Some("three state"),3)
    , Customer(4,"four",Some("four address"),Some("four city"),None,4)
  )

  def main(args: Array[String]): Unit = {

    println(s"Customers converted into json string:\n ${customers.asJson}")

    val jsonString =
      """[
        |   {"city":"one city","name":"one","state":"one state","user_id":1,"id":1,"address":"one street"}
        |   ,{"city":"two city","name":"two","state":"two state","user_id":2,"id":2}
        |   ,{"name":"three","state":"three state","user_id":3,"id":3,"address":"three address"}
        |   ,{"city":"four city","name":"four","user_id":4,"id":4,"address":"four address"}
        |]""".stripMargin


    var parsed: Option[List[Customer]] = jsonString.decodeOption[List[Customer]]

    println(s"Json string turned back into customers:\n ${parsed.get}")

  }
}

The developers are also helpful and responsive to folks getting started.

Solution 4

There is now a fork of Jerkson that supports Scala 2.10 at https://github.com/randhindi/jerkson.

Solution 5

So, based on the absence of an error message and the incorrect sample code, I'm suspecting this is more of an issue of just not understanding how the lift-json extraction works. If I've misunderstood, do comment and let me know. So, if I'm right then here's what you need.

To serialize:

import net.liftweb.json._
  import Extraction._

implicit val formats = DefaultFormats

case class Person(...)
val person = Person(...)
val personJson = decompose(person) // Results in a JValue

Then to reverse the process you'd do something like:

// Person Json is a JValue here.
personJson.extract[Person]

If that's not the part you're having trouble with, then do let me know and I can try to revise my answer to be more helpful.

Share:
41,056
user1698607
Author by

user1698607

Updated on October 22, 2020

Comments

  • user1698607
    user1698607 over 3 years

    Scala 2.10 seems to have broken some of the old libraries (at least for the time being) like Jerkson and lift-json.

    The target usability is as follows:

    case class Person(name: String, height: String, attributes: Map[String, String], friends: List[String])
    
    //to serialize
    val person = Person("Name", ....)
    val json = serialize(person)
    
    //to deserialize
    val sameperson = deserialize[Person](json)
    

    But I'm having trouble finding good existing ways of generating and deserializing Json that work with Scala 2.10.

    Are there best practice ways of doing this in Scala 2.10?

  • Kipton Barros
    Kipton Barros over 11 years
    I believe the issue is that Scala 2.10 breaks binary compatibility, and lift-json is not currently up to date.
  • Sebastien Lorber
    Sebastien Lorber about 11 years
    Jackson scala module isn't good for Enumerations: stackoverflow.com/questions/15887785/…
  • Kipton Barros
    Kipton Barros about 11 years
    This might be fixed in the nightly build: github.com/FasterXML/jackson-module-scala/wiki/Enumerations
  • Alan Coromano
    Alan Coromano almost 11 years
    how do I install it and use in IntelliJ Idea 12?
  • Sebastian Ganslandt
    Sebastian Ganslandt almost 11 years
    Can't help you with that one I'm afraid. I ended up going for spray-json instead of jerkson.
  • Alan Coromano
    Alan Coromano almost 11 years
    it doesn't matter, how did you install it?
  • Sebastian Ganslandt
    Sebastian Ganslandt almost 11 years
    I never did install jerkson, just noted that there was a newer verion out there :). If you're referring spray-json, I just pointed it out as an sbt dependency.
  • Alan Coromano
    Alan Coromano almost 11 years
    I'm asking, how I install spray-json ?
  • Sebastian Ganslandt
    Sebastian Ganslandt almost 11 years
    What do you mean by installing? If you are using some sort of dependency managing build tool you just add it as a dependency. For sbt, see github.com/spray/spray-json under installation.
  • jpswain
    jpswain almost 11 years
    lift-json for 2.10 is now available and up to date! working well for me now. see this: liftweb.net/25
  • Mermoz
    Mermoz over 10 years
    erasure seems deprecated
  • simbo1905
    simbo1905 over 10 years
    the deprecated m.erasure can be changed to m.runtimeClass in two places and it works great