Showing posts with label json. Show all posts
Showing posts with label json. Show all posts

Sunday, August 30, 2009

JSON Serialization for Scala Objects

SJSON : JSON Serialization for Scala Objects has just been published on the Github. It uses the awesome dispatch-json of Nathan Hamblen as the base JSON processor.

Here's the idea ..

I have a Scala object as ..

val addr = Address("Market Street", "San Francisco", "956871")

which I would like to store as JSON and retrieve as plain old Scala object. Here's the simple assertion that I would like to have as invariant ..

addr should equal(
  serializer.in[Address](serializer.out(addr)))


There are situations, particularly when writing generic libraries, when I don't know what class to serialize into. I can do that as well ..

serializer.in[AnyRef](serializer.out(addr))

or just as ..

serializer.in(serializer.out(addr))

What you get back from is a JsValue, an abstraction of the JSON object model. You can use extractors to get back individual attributes ..

val a = serializer.in[AnyRef](serializer.out(addr))

// use extractors
val c = 'city ? str
val c(_city) = a
_city should equal("San Francisco")

val s = 'street ? str
val s(_street) = a
_street should equal("Market Street")

val z = 'zip ? str
val z(_zip) = a
_zip should equal("956871")


Serialization of Embedded Objects

Suppose you have the following Scala classes .. Here Contact has an embedded Address Map ..

@BeanInfo
case class Contact(name: String, 
                   @JSONTypeHint(classOf[Address])
                   addresses: Map[String, Address]) {
  
  private [json] def this() = this(null, null)
  
  override def toString = "name = " + name + " addresses = " + 
    addresses.map(=> a._1 + ":" + a._2.toString).mkString(",")
}

@BeanInfo
case class Address(street: String, city: String, zip: String) {
  private [json] def this() = this(null, null, null)
  
  override def toString = "address = " + street + "/" + city + "/" + zip
}


With SJSON, I can do the following ..

val a1 = Address("Market Street", "San Francisco", "956871")
val a2 = Address("Monroe Street", "Denver", "80231")
val a3 = Address("North Street", "Atlanta", "987671")

val c = Contact("Bob", Map("residence" -> a1, "office" -> a2, "club" -> a3))
val co = serializer.out(c)

// with class specified
c should equal(serializer.in[Contact](co))

  // no class specified
val a = serializer.in[AnyRef](co)

// extract name
val n = 'name ? str
val n(_name) = a
"Bob" should equal(_name)

// extract addresses
val addrs = 'addresses ? obj
val addrs(_addresses) = a

// extract residence from addresses
val res = 'residence ? obj
val res(_raddr) = _addresses

// make an Address bean out of _raddr
val address = JsBean.fromJSON(_raddr, Some(classOf[Address]))
a1 should equal(address)

object r { def >[T](f: JsF[T]) = f(a.asInstanceOf[JsValue]) }

// still better: chain 'em up
"Market Street" should equal(
  (>{ ('addresses ? obj) andThen ('residence ? obj) andThen ('street ? str) }))


Feel free to fork, contribute and enjoy!