Parsing and serializing case classes is vey straightforward with ScalaJack. Serializing is simply a call to toJson():
// Classes must be defined in separate file. Sorry--it's a macro thing
// case class Person(name:String, age:Int)
given sjPerson: ScalaJack[Person] = sjCodecOf[Person]
val js = sjPerson.toJson(Person("Mike",32))
// renders {"name":"Mike","age":32}
All basic Scala primitive data types are supported in addition to Java primitives and Java 8+'s java.time classes. Collections are supported as well. (Note: Java classes must conform to JavaBeans standards for getters/setters, i.e. ScalaJack presumes Java classes are JavaBeans.)
Of course you can nest collections and case classes as you like:
// case class Hobby(desc:String, cost:Double)
// case class Person(name:String, age:Int, hobbies:List[Hobby])
given sjPerson: ScalaJack[Person] = sjCodecOf[Person]
val js = sjPerson.toJson(Person("Mike",32,List(Hobby("surfing",1000.0))))
// renders {"name":"Mike","age":32,"hobbies":[{"desc":"surfing","cost":1000.0}]}
Parsing JSON into a case class is also simple:
val js = """{"name":"Mike","age":32}"""
val person = sjPerson.fromJson(js)
This works for collections too:
given sjListOfPerson: ScalaJack[List[Person]] = sjCodecOf[List[Person]]
given sjMapOfPerson: ScalaJack[Map[Person]] = sjCodecOf[Map[Person]]
val js = """[
{"name":"Mike","age":32},
{"name":"Sarah","age":29}
]"""
val persons = sjListOfPersion.fromJson(js)
val js2 = """{
"surfers":[{"name":"Mike","age":32}]
}"""
val byHobby = sjMapOfPersion.fromJson(js)
So you can see the combinations can be as complex as you need.
ScalaJack 8 works with sealed traits only.
This is a significant departure from previous versions of ScalaJack, which handled any trait, sealed or not. ScalaJack 8 is almost entirely a macro, which means everything about serialization must be known at compile-time. For a non-sealed trait, we can add a type hint to rendered JSON, but upon parsing that JSON we would have to retrieve the type hint and materialize the concrete class given by the hint. Problem is: we won't know what class that is until runtime, so ScalaJack 8's macro infrastructure has nothing to work with at compile-time. In order to support non-sealed traits, like older ScalaJack, we would need to create an entire complimentary mirror of ScalaJack 8 that ran only at runtime. At this time, we're not convinced the level of work required to accomplish this would be worth the use case.
Example of sealed trait use:
Pet.scala
sealed trait Pet{ val name:String; val numLegs:Int }
case class Dog(name:String, numLegs:Int) extends Pet
case class Cat(name:String, numLegs:Int, numberOfNaps: Int) extends Pet
MyProg.scala
given sjDog: ScalaJack[Dog] = sjCodecOf[Dog] // not needed to serialize Pet--only here to show difference!
given sjPet: ScalaJack[Pet] = sjCodecOf[Pet] // render trait Pet (any of its sealed children: Cat or Dog)
val inst:Pet = Dog("Fido",4)
// Render Dog as a case class
val jsDog = sjDog.toJson(inst) // renders {"name":"Fido","numLegs":4}
sjDog.fromJson(jsDog)
// Render as a trait
val jsPet = sjPet.toJson(inst) // renders {"_hint":"Dog","name":"Fido","numLegs":4}
sjPet.fromJson(jsPet)
// Oops!
sjDog.fromJson(jsDog) // Fails. No type hint means ScalaJack doesn't know which Pet to make
The given type hint will allow ScalaJack to construct a Dog object and return it as a Pet, which is what you want.
We'll see in a later section how you can customize both the type hint label and the the hint value in your JSON.