You don't need a case class Example
, in µPickle you can create a json mixing manual construction and case-class construction
import upickle.default.{macroRW, ReadWriter, write, transform} // "com.lihaoyi" %% "ujson" % "0.9.6"
case class Student(
name: String,
age: Int
)
object Student {
implicit val rw: ReadWriter[Student] = macroRW
}
ujson.Obj(
"student1" -> write(Student("name",12)),
"student2" -> write(Student("name2",13))
)
//{"student1":"{\"name\":\"name\",\"age\":12}","student2":"{\"name\":\"name2\",\"age\":13}"}
ujson.Obj(
"student1" -> transform(Student("name",12)).to[ujson.Value],
"student2" -> transform(Student("name2",13)).to[ujson.Value]
)
//{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13}}
If [key:String]: Student
means Map[String, Student]
then µPickle seems to support this out-of-the-box
case class Example(
m: Map[String, Student],
_logOp: Option[Boolean] = Some(false),
queryName: String
)
object Example {
implicit val rw: ReadWriter[Example] = macroRW
}
write(Example(
Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
Some(true),
"abc"
))
//{"m":{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13}},"_logOp":[true],"queryName":"abc"}
It shouldn't be nested within m
You can achieve this with a custom codec (pickler)
import upickle.default.{ReadWriter, macroRW, readwriter, transform, write, read}
import scala.collection.mutable
case class Example(
m: Map[String, Student],
_logOp: Option[Boolean] = Some(false),
queryName: String
)
object Example {
implicit val rw: ReadWriter[Example] = {
val standardExampleRW = macroRW[Example]
readwriter[ujson.Value].bimap[Example](
example => transform[Example](example)(standardExampleRW).to[ujson.Value] match {
case ujson.Obj(standardMap) =>
val newMap = mutable.LinkedHashMap.empty[String, ujson.Value]
standardMap.remove("m")
newMap.addAll(example.m.map { case (str, stud) => str -> transform[Student](stud).to[ujson.Value]})
.addAll(standardMap)
ujson.Obj(newMap)
},
// if you don't need a reversed transform i.e. from a json to an Example then you can omit this part
// _ => ???
{
case ujson.Obj(newMap) =>
val logOpJson = newMap.remove("_logOp")
val logOp = logOpJson.map(transform[ujson.Value](_).to[Option[Boolean]])
val queryNameJson = newMap.remove("queryName")
val queryName = queryNameJson.map(transform[ujson.Value](_).to[String]).getOrElse("")
val m = newMap.map { case (str, json) => str -> transform[ujson.Value](json).to[Student] }.toMap
logOp.map(Example(m, _, queryName)).getOrElse(Example(m, queryName = queryName))
}
)
}
}
write(Example(
Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
Some(true),
"abc"
))
//{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}
read[Example](
"""{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}"""
)
//Example(Map(student1 -> Student(name,12), student2 -> Student(name2,13)),Some(true),abc)
So, basically you can generate case classes in Scala but it's not necessary for serialization into json format.
Just for the completeness, since your original question was how to define a case class, here is a code with actual case-class definition. But this code is slow (since it uses runtime reflection and runtime compilation) and is not conventional Scala code (on contrary to the above custom picklers)
case class Example(
m: Map[String, Student],
_logOp: Option[Boolean] = Some(false),
queryName: String
)
import scala.reflect.runtime.{currentMirror => rm} // libraryDependencies += scalaOrganization.value % "scala-reflect" % "2.13.10"
import scala.reflect.runtime.universe.{Quasiquote, TermName, typeOf, termNames}
import scala.tools.reflect.{ToolBox, FrontEnd} // libraryDependencies += scalaOrganization.value % "scala-compiler" % "2.13.10"
val tb = rm.mkToolBox(
// frontEnd = new FrontEnd {
// override def display(info: Info): Unit = println(info)
// },
// options = "-d out"
)
implicit val rw: ReadWriter[Example] =
readwriter[ujson.Value].bimap[Example](
example => {
val studentFields = example.m.keys.map(str =>
q"val ${TermName(str)}: ${typeOf[Student]}"
)
val students = example.m.values.toSeq
val fields = studentFields ++ Seq(
q"val _logOp: Option[Boolean] = Some(false)",
q"val queryName: String"
)
val classSymbol = tb.define(q"case class Example1(..$fields)").asClass
val constructorSymbol =
classSymbol.typeSignature.decl(termNames.CONSTRUCTOR).asMethod
val classInstance = tb.mirror.reflectClass(classSymbol)
.reflectConstructor(constructorSymbol)
.apply(students ++ Seq(example._logOp, example.queryName): _*)
tb.eval(q"""
import upickle.default._
implicit val rw: ReadWriter[$classSymbol] = macroRW[$classSymbol]
transform[$classSymbol](_: $classSymbol).to[ujson.Value]
""").asInstanceOf[Any => ujson.Value].apply(classInstance)
},
json => ???
)
val x = write(Example(
Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
Some(true),
"abc"
))
//{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}
q"..."
is a string interpolator for quasiquotes (creating abstract syntax trees).