Most popular JSON libraries for Scala have the ability to serialize and deserialize to case classes.
Unfortunately, until Scala 2.11 is released, there is a restriction
If you're using lenses anyway, just nest your case classes. You'll have more potential for reuse of data, and the main reason not to nest is to avoid monstrosities like
record.copy(person = record.person.copy(name = record.person.name.capitalize))
which are (largely) solved if you use lenses. JSON can handle nested classes.
It seems to be that, by defining a copy function (unfortunately by hand), regular classes can work with lenses, as Travis mentioned in his comment to the question, above.
Below is a proof of concept that works (using json4s and a copy of an old Scalaz lens implementation, borrowed from Daniel Sobral's answer to Cleaner way to update nested structures):
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.native.JsonMethods._
import native.Serialization.write
class Parent(val name:String, val age:Int, val kids:List[Kid]){
override def toString() = s"""$name is $age years old, her/his kids are ${kids.mkString(", ")}."""
def copy(name:String = name, age:Int = age, kids:List[Kid] = kids) =
new Parent(name, age, kids)
}
class Kid(val name:String, val age:Int){
override def toString() = s"""$name ($age)"""
def copy(name:String = name, age:Int = age) =
new Kid(name, age)
}
object TestJson {
implicit val formats = DefaultFormats
val json = """{"name":"John", "age":41, "kids":[{"name":"Mary", "age":10}, {"name":"Tom", "age":7}]}"""
def main(args: Array[String]): Unit = {
val parentKidsLens = Lens(
get = (_: Parent).kids,
set = (p: Parent, kids: List[Kid]) => p.copy(kids = kids))
val firstKidLens = Lens(
get = (_: List[Kid]).head,
set = (kds: List[Kid], kid: Kid) => kid :: kds.tail)
val kidAgeLens = Lens(
get = (_: Kid).age,
set = (k: Kid, age: Int) => k.copy(age = age))
val parentFirstKidAgeLens = parentKidsLens andThen firstKidLens andThen kidAgeLens
println( parentFirstKidAgeLens.mod(parse(json).extract[Parent], age => age + 1) )
}
}
case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
def apply(whole: A): B = get(whole)
def updated(whole: A, part: B): A = set(whole, part)
def mod(a: A, f: B => B) = set(a, f(this(a)))
def compose[C](that: Lens[C,A]) = Lens[C,B](
c => this(that(c)),
(c, b) => that.mod(c, set(_, b))
)
def andThen[C](that: Lens[B,C]) = that compose this
}