案例 Class 到 Json:关于如何在不使用外部 Json 库的情况下避免重复模式匹配的设计模式

Case Class to Json: Design Pattern on How to Avoid Repetitive Pattern Match without Using External Json Lib

我正在学习 Scala 案例 classes 和设计模式。为此,我在下面创建了一个示例,我认为在使用 Json 类型数据时,这是一个相当可能的场景。我知道存在执行此操作的库,但我手动执行此操作是为了学习 Scala 解决问题的方法,因为使用库不会帮助我学习。

我想做的主要设计改进是抽象通用代码。

假设我的代码库由许多案例 class 组成,其中每个案例 class 都是可序列化的:

trait Base {
    def serialize(): String
  }

  trait Animal extends Base
  trait Mammal extends Animal
  trait Reptile extends Animal

  case class Lizard(name: String, tail: Boolean) extends Reptile {
    def serialize(): String = s"""{name: $name, tail: $tail}"""
  }

  case class Cat(name: String, age: Int) extends Mammal {
    def serialize(): String = s"""{name: $name, age: $age}"""
  }

  case class Fish(species: String) extends Animal {
    def serialize(): String = s"""{species: $species}"""
  }

  case class Pets(group_name: String, cat: Option[Cat] = None, lizard: Option[Lizard] = None, fish: Fish) extends Base {
    def serialize(): String = {

      // cat and lizard serialize in a similar fashion
      val cat_object = cat match {
        case Some(c) => s"""cats: ${c.serialize()}"""
        case _ => ""
      }

      val lizard_object = lizard match {
        case Some(d) => s"""lizards: ${d.serialize()}"""
        case _ => ""
      }

      // fish serializes in a different way as it is not an option
      val fish_object = s"""fish: ${fish.serialize()}"""

      s"""{$lizard_object, $cat_object, $fish_object}"""
    }
  }

  val bob = Cat("Bob", 42)
  val jill = Lizard("Jill", true)

  val pets = Pets("my group", Some(bob), Some(jill), Fish("goldfish")).serialize()

  println(pets)
}

现在这里有一个重复的模式:

  1. 在 Pets 中,当我进行序列化时,我基本上遍历参数列表中的每个(键,值)对(group_name 除外)并执行以下操作:

    键:value.serialize()

现在我不知道值的形式,它可以像示例中那样是一个选项。此外,假设我有很多 class 像宠物。在那种情况下,我将不得不在每个需要的参数上手动编写许多模式匹配,区分 String、Int、Option[String] 等。是否有一种方法可以抽象出这个可序列化的操作,这样如果我有很多 case class就像宠物一样,我可以简单地 运行 一个函数并得到正确的结果。

我在这里问了一个关于从案例 classes 中获取声明字段的相关问题,但似乎这种方式类型不安全,如果我添加更多自定义案例 [=41],可能会在以后产生问题=]es:

这通常是一件棘手的事情。此代码并不总是使用输出中的所有字段(例如 group_name)并且字段名称并不总是与字符串中的名称匹配(例如 catcats

然而,有一些 Scala 技巧可以使现有代码更简洁:

trait Base {
  def serial: String
}

trait Animal extends Base
trait Mammal extends Animal
trait Reptile extends Animal

case class Lizard(name: String, tail: Boolean) extends Reptile {
  val serial: String = s"""{name: $name, tail: $tail}"""
}

case class Cat(name: String, age: Int) extends Mammal {
  val serial: String = s"""{name: $name, age: $age}"""
}

case class Fish(species: String) extends Animal {
  val serial: String = s"""{species: $species}"""
}

case class Pets(group_name: String, cat: Option[Cat] = None, lizard: Option[Lizard] = None, fish: Fish) extends Base {
  val serial: String = {
    // cat and lizard serialize in a similar fashion
    val cat_object = cat.map("cats: " + _.serial)

    val lizard_object = lizard.map("lizards: " + _.serial)

    // fish serializes in a different way as it is not an option
    val fish_object = Some(s"""fish: ${fish.serial}""")

    List(lizard_object, cat_object, fish_object).flatten.mkString("{", ", ", "}")
  }
}

val bob = Cat("Bob", 42)
val jill = Lizard("Jill", true)

val pets = Pets("my group", Some(bob), Some(jill), Fish("goldfish")).serial

println(pets)

由于 case class 是不可变的,序列化的值不会改变,所以让它看起来像 属性 更有意义 serial.

Option 值最好在 Option 中使用 map 处理,然后在最后提取。在这种情况下,我使用 flattenList[Option[String]] 变成 List[String].

如果其中一个选项为空,mkString 方法是格式化列表和避免在输出中出现 , , 的好方法。

这是为案例 类 制作有限序列化方法的通用方法,利用它们是 Product 的事实。

def basicJson(a: Any): String = a match {
  case Some(x) => basicJson(x)
  case None    => ""
  case p: Product =>
    (p.productElementNames zip p.productIterator.map(basicJson _))
      .map(t => s"${t._1}: ${t._2}")
      .mkString("{", ", ", "}")
  case _       => a.toString
}

如果你想序列化 Pets 而没有组名,你可以在 Pets 中定义一个 val 来手动序列化除 group_name 之外的所有字段:

val toJson = s"{cat: ${basicJson(cat)}, lizard: ${basicJson(lizard)}, fish: ${basicJson(fish)}}"

这段代码的输出

val bob = Cat("Bob", 42)
val jill = Lizard("Jill", true)

val pets = Pets("my group", Some(bob), Some(jill), Fish("goldfish"))

println(pets.toJson)

这是:

{cat: {name: Bob, age: 42}, lizard: {name: Jill, tail: true}, fish: {species: goldfish}}

在斯卡斯蒂:https://scastie.scala-lang.org/A5slCY65QIKJ2YTNBUPvQA

<script src="https://scastie.scala-lang.org/A5slCY65QIKJ2YTNBUPvQA.js"></script>

请记住,除了 case 类 之外,这对任何其他情况都不起作用 - 您必须使用反射。