Skip to content

Examples of Type Class based JSON serialization

debasishg edited this page Feb 15, 2011 · 1 revision

Here are some examples that will help in understanding how the type class based serialization occurs in sjson :-

Case 1: A case class that acts as a simple wrapper to one field

Here's a sample session in the REPL that illustrates how you serialize and de-serialize:

scala> case class Name(name: String)
defined class Name

scala> import sjson.json._
import sjson.json._

scala> import DefaultProtocol._
import DefaultProtocol._

scala> implicit val NameFormat: Format[Name] = wrap[Name, String]("name")(_.name, Name)
NameFormat: sjson.json.Format[Name] = sjson.json.Generic$$anon$2@1eed0fb

scala> val n = Name("debasish ghosh")
n: Name = Name(debasish ghosh)

scala> import JsonSerialization._
import JsonSerialization._

scala> val js = tojson(n)
js: dispatch.json.JsValue = {"name" : "debasish ghosh"}

scala> dispatch.json.JsValue.toJson(js)
res0: String = {"name" : "debasish ghosh"}

scala> fromjson[Name](js)
res1: Name = Name(debasish ghosh)

scala> res1 == n
res2: Boolean = true

Note in the above example, wrap is the API that provides the type class implementation methods reads and writes. But all that's taken care of by the magic of implicits.

Case 2: A case class that wraps a few simple values

Here's the session in REPL:

scala> case class Shop(store: String, item: String, price: Int)
defined class Shop

scala> implicit val ShopFormat: Format[Shop] = asProduct3("store", "item", "price")(Shop)(Shop.unapply(_).get)
ShopFormat: sjson.json.Format[Shop] = sjson.json.Generic$$anon$4@42a751

scala> val shop = Shop("Shoppers Stop", "dress material", 1000)
shop: Shop = Shop(Shoppers Stop,dress material,1000)

scala> tojson(shop)
res3: dispatch.json.JsValue = {"store" : "Shoppers Stop", "item" : "dress material", "price" : 1000}

scala> dispatch.json.JsValue.toJson(res3)
res5: String = {"store" : "Shoppers Stop", "item" : "dress material", "price" : 1000}

scala> fromjson[Shop](res3)
res6: Shop = Shop(Shoppers Stop,dress material,1000)

scala> res6 == shop
res7: Boolean = true

In Scala, a Product of 3 elements having types A, B and C is a class whose instances are all possible elements of the cartesian product of A, B and C. In the above example Shop is a Product3 and the method asProduct3 supplies the type class implementation methods for it.

Case 3: A case class that contains a generic collection

Here's the sample REPL session:

scala> case class Address(street: String, city: String, zip: String)
defined class Address

scala> implicit val AddressFormat: Format[Address] = 
     |       asProduct3("street", "city", "zip")(Address)(Address.unapply(_).get)
AddressFormat: sjson.json.Format[Address] = sjson.json.Generic$$anon$4@c00e55

scala> case class Contact(name: String, addresses: List[Address])
defined class Contact

scala> implicit val ContactFormat: Format[Contact] = 
     |       asProduct2("name", "addresses")(Contact)(Contact.unapply(_).get)
ContactFormat: sjson.json.Format[Contact] = sjson.json.Generic$$anon$3@f61f70

scala> val contact = Contact("Debasish Ghosh", 
     |         List(Address("monroe st", "denver", "80231"), Address("pine drive", "santa clara", "95054")))
contact: Contact = Contact(Debasish Ghosh,List(Address(monroe st,denver,80231), Address(pine drive,santa clara,95054)))

scala> tojson(contact)
res8: dispatch.json.JsValue = {"name" : "Debasish Ghosh", "addresses" : [{"street" : "monroe st", "city" : "denver", "zip" : "80231"}, {"street" : "pine drive", "city" : "santa clara", "zip" : "95054"}]}

scala> dispatch.json.JsValue.toJson(res8)
res9: String = {"name" : "Debasish Ghosh", "addresses" : [{"street" : "monroe st", "city" : "denver", "zip" : "80231"}, {"street" : "pine drive", "city" : "santa clara", "zip" : "95054"}]}

scala> fromjson[Contact](res8)
res10: Contact = Contact(Debasish Ghosh,List(Address(monroe st,denver,80231), Address(pine drive,santa clara,95054)))

scala> res10 == contact
res11: Boolean = true

As can be seen from above, type class implementations for all built-in types like List etc. are automatically made available through implicits. For your own types, you need to define your Format implementation (as we did with Address above).

Case 4: Tuple serialization

Here's a sample session at the REPL:

scala> import sjson.json._
import sjson.json._

scala> import DefaultProtocol._
import DefaultProtocol._

scala> val t1 = ("debasish", 12)
t1: (java.lang.String, Int) = (debasish,12)

scala> import JsonSerialization._
import JsonSerialization._

scala> tojson(t1)                
res1: dispatch.json.JsValue = ["debasish", 12]

scala> dispatch.json.JsValue.toJson(res1)
res2: String = ["debasish", 12]

scala> fromjson[Tuple2[String, Int]](res1)
res3: (String, Int) = (debasish,12)

scala> res3 == t1
res4: Boolean = true

Case 5: Serialization of Scala Collections

Mutable Sets

scala> import scala.collection._
import scala.collection._

scala> val s = mutable.Set(12, 13, 10, 23, 25)
s: scala.collection.mutable.Set[Int] = Set(12, 25, 23, 13, 10)

scala> tojson(s)
res5: dispatch.json.JsValue = [13, 25, 12, 10, 23]

scala> dispatch.json.JsValue.toJson(res5)     
res6: String = [13, 25, 12, 10, 23]

scala> fromjson[mutable.Set[Int]](res5)
res7: scala.collection.mutable.Set[Int] = Set(12, 25, 23, 13, 10)

scala> s == res7
res8: Boolean = true

Immutable Sets

scala> import scala.collection._              
import scala.collection._

scala> val t = immutable.Set(12, 13, 10, 23, 25) 
t: scala.collection.immutable.Set[Int] = Set(12, 23, 13, 10, 25)

scala> tojson(t)                                
res9: dispatch.json.JsValue = [12, 23, 13, 25, 10]

scala> dispatch.json.JsValue.toJson(res9)       
res10: String = [12, 23, 13, 25, 10]

scala> fromjson[immutable.Set[Int]](res9)       
res11: scala.collection.immutable.Set[Int] = Set(12, 23, 13, 10, 25)

scala> t == res11
res12: Boolean = true

Maps

scala> import sjson.json._
import sjson.json._

scala> import DefaultProtocol._
import DefaultProtocol._

scala> import JsonSerialization._
import JsonSerialization._

scala> val m = Map("100" -> "dg", "200" -> "mc")
m: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map((100,dg), (200,mc))

scala> tojson(m)                                
res1: dispatch.json.JsValue = {"100" : "dg", "200" : "mc"}

scala> dispatch.json.JsValue.toJson(res1)
res2: String = {"100" : "dg", "200" : "mc"}

scala> fromjson[Map[String, String]](res1)
res3: Map[String,String] = Map((100,dg), (200,mc))

scala> res3 == m
res4: Boolean = true

Case 6: Inheritance & JSON Serialization

You can do that too. Here's a sample REPL session:

a. Define the base class and the type class implementation for it:

scala> import sjson.json._
import sjson.json._

scala> import JsonSerialization._
import JsonSerialization._

scala> import DefaultProtocol._
import DefaultProtocol._

scala> case class Address(street: String, city: String, zip: String)
defined class Address

scala>   implicit val AddressFormat: Format[Address] = 
     |     asProduct3("street", "city", "zip")(Address)(Address.unapply(_).get)
AddressFormat: sjson.json.Format[Address] = sjson.json.Generic$$anon$4@eae692

scala> case class Base(no: String, name: String, addresses: Array[Address])    
defined class Base

scala> implicit val BaseFormat: Format[Base] =                                 
     |     asProduct3("no", "name", "addresses")(Base)(Base.unapply(_).get)
BaseFormat: sjson.json.Format[Base] = sjson.json.Generic$$anon$4@13f957a

b. Now define the derived class and it's protocol in terms of the base class format:

scala> class Derived(no: String, name: String, addresses: Array[Address], special: Boolean) extends Base(no, name, addresses) {
     |   val specialFlag = special
     | }
defined class Derived

scala> object DerivedProtocol extends DefaultProtocol {
     |     import dispatch.json._
     |     import JsonSerialization._
     |     implicit object DerivedFormat extends Format[Derived] {
     |       def reads(json: JsValue): Derived = {
     |         val b = fromjson[Base](json)
     |         json match {
     |           case JsObject(m) =>
     |             new Derived(b.no, b.name, b.addresses,
     |               fromjson[Boolean](m(JsString("specialFlag"))))
     |           case _ => throw new RuntimeException("JsObject expected")
     |         }
     |       }
     |       def writes(a: Derived): JsValue = {
     |         val o = tojson(a: Base)
     |         val JsObject(m) = o
     |         JsObject(m ++ List((tojson("specialFlag").asInstanceOf[JsString], tojson(a.specialFlag))))
     |       }
     |     }
     |   }
defined module DerivedProtocol

c. Now serialize:

scala> import DerivedProtocol._
import DerivedProtocol._

scala> val sa = new Derived("123", "debasish ghosh", 
  Array(Address("monroe st", "denver", "80231"), Address("tamarac st", "boulder", "80231")), true)
sa: Derived = Base(123,debasish ghosh,[LAddress;@ad7990)

scala> val acc = fromjson[Derived](tojson(sa))                                                                                                       
acc: Derived = Base(123,debasish ghosh,[LAddress;@a528bc)