Scala 2.13: Case类与可扩展的变量属性?



我想创建一个case类,它可以合并一个字符串记录和另一个case类实体。

例如:

case class Student(
name: String
age: Int
)
case class Example(
[key:String]: Student
)

现在我想使用Example添加多个属性,其中attribute可以有N个元素,但所有这些属性的类型仍然是学生。下面是一个例子:

Example(student1 = Student("name",12),student2=Student("name2",13))

我使用Case类的原因是我需要使用UPickle库将其转换为JSON,所以我想知道实现相同目标的可行性。

请注意,Example类不仅包含[key:String]: Student属性类型,还包括以下内容:

case class Example(
[key:String]: Student,
_logOp: Option[Boolean] = false,
queryName: String,
...
)

case类的转换结果:

case class Example(
_logOp: String,
variation: String,
[key:String]: FiltersCaseClass 
/* This line I have added to simplify and make my problem more understandable. Basically the case class would contain some properties like `_logOp` `variation` and then a lot of keys with their values as another case class `FilterCaseClass`
*/
)

应该看起来像这样:

{"_logOp":"AND","variation": "en","ids": {"_logOp": "OR","_expressions": [{"value": "242424"},{"value": "242422"}]}}

其中FilterCaseClass为:

case class FilterCaseClass(
_logOp: String,
_expressions: Seq[SingleValueFilter]
)

其中SingleValueFilter是另一个包含值的case类

编辑1:

根据Dymtro的一个答案:

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"}

这里我想要的唯一区别是:

{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}

不同之处在于,我希望case类能够灵活地添加Student类的键值对。

你不需要一个case classExample,在µPickle中你可以创建一个json混合手工构造和case-class构造

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}}

如果[key:String]: Student意味着Map[String, Student],那么µPickle似乎支持这个开箱即用的

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"}

不应该嵌套在m

可以使用自定义编解码器(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)

所以,基本上你可以在Scala中生成case类,但没有必要序列化成json格式。


只是为了完整起见,因为您最初的问题是如何定义一个case类,这里是一个具有实际case类定义的代码。但是这段代码很慢(因为它使用了运行时反射和运行时编译),并且不是传统的Scala代码(与上面的自定义pickler相反)

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"..."是一个准引号字符串插入器(创建抽象语法树)。

最新更新