我想构建一个Scala DSL,从Java POJO的现有结构转换为相当于Map的结构。
但是传入的对象结构很可能包含很多空引用,这将导致输出映射中没有值。
在这种情况下,性能非常重要,所以我需要避免反射和抛出/捕获NPE。
我已经考虑过this topic这不符合我的要求。
我认为答案可能在于使用宏来生成一些特殊类型,但我没有使用scala宏的经验。
更正式:
项目提供的POJO类:(将有50个POJO,嵌套,所以我想要一个不需要手工编写和维护每个类或特征的解决方案)
case class Level1(
@BeanProperty var a: String,
@BeanProperty var b: Int)
case class Level2(
@BeanProperty var p: Level1,
@BeanProperty var b: Int)
预期行为:
println(convert(null)) // == Map()
println(convert(Level2(null, 3))) // == Map("l2.b" -> 3)
println(convert(Level2(Level1("a", 2), 3))) // == Map(l2.p.a -> a, l2.p.b -> 2, l2.b -> 3)
正确的实现,但我想要一个更简单的DSL来编写映射
implicit def toOptionBuilder[T](f: => T) = new {
def ? : Option[T] = Option(f)
}
def convert(l2: Level2): Map[String, _] = l2? match {
case None => Map()
case Some(o2) => convert(o2.p, "l2.p.") + ("l2.b" -> o2.b)
}
def convert(l1: Level1, prefix: String = ""): Map[String, _] = l1? match {
case None => Map()
case Some(o1) => Map(
prefix + "a" -> o1.a,
prefix + "b" -> o1.b)
}
以下是我想用DSL编写的内容:
def convertDsl(l2:Level2)={
Map(
"l2.b" -> l2?.b,
"l2.p.a" -> l2?.l1?.a,
"l2.p.b" -> l2?.l1?.b
)
}
请注意,我可以使用'?'指定属性是可选的。 我想要的是使用宏静态生成方法l2.?l1或l2?.l1返回选项[Level1](因此类型检查在我的DSL中正确完成)。
答案 0 :(得分:0)
我无法将其精确到您上面给出的语法,但通常,这样的事情可能有效:
sealed trait FieldSpec
sealed trait ValueFieldSpec[+T] extends FieldSpec
{
def value: Option[T]
}
case class IntFieldSpec(value: Option[Int]) extends ValueFieldSpec[Int]
case class StringFieldSpec(value: Option[String]) extends ValueFieldSpec[String]
case class Level1FieldSpec(input: Option[Level1]) extends FieldSpec
{
def a: ValueFieldSpec[_] = StringFieldSpec(input.map(_.a))
def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
}
case class Level2FieldSpec(input: Option[Level2]) extends FieldSpec
{
def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
def l1 = Level1FieldSpec(input.map(_.p))
}
case class SpecArrowAssoc(str: String)
{
def ->(value: ValueFieldSpec[_]) = (str, value)
}
implicit def str2SpecArrowAssoc(str: String) = SpecArrowAssoc(str)
implicit def Level2ToFieldSpec(input: Option[Level2]) = Level2FieldSpec(input)
def map(fields: (String, ValueFieldSpec[_])*): Map[String, _] =
Map[String, Any]((for {
field <- fields
value <- field._2.value
} yield (field._1, value)):_*)
def convertDsl(implicit l2: Level2): Map[String, _] =
{
map(
"l2.b" -> l2.?.b,
"l2.p.a" -> l2.?.l1.a,
"l2.p.b" -> l2.?.l1.b
)
}
然后我们得到:
scala> val myL2 = Level2(Level1("a", 2), 3)
myL2: Level2 = Level2(Level1(a,2),3)
scala> convertDsl(myL2)
res0: scala.collection.immutable.Map[String,Any] = Map(l2.b -> 3, l2.p.a -> a, l2.p.b -> 2)
请注意,DSL使用'。?'而不只是'?'作为我能看到Scala使用分号推理和后缀运算符的麻烦的唯一方法(参见,例如,@ 0__对scala syntactic suger question的回答)。
此外,您可以提供的字符串是任意的(不会检查或解析它们),这种简单的“FieldSpec”层次结构将假设您的所有POJO对字符串字段使用'a'而对Int字段使用'b'等
经过一些时间和努力,我确信可以改进。