案例 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)
}
现在这里有一个重复的模式:
在 Pets 中,当我进行序列化时,我基本上遍历参数列表中的每个(键,值)对(group_name 除外)并执行以下操作:
键:value.serialize()
现在我不知道值的形式,它可以像示例中那样是一个选项。此外,假设我有很多 class 像宠物。在那种情况下,我将不得不在每个需要的参数上手动编写许多模式匹配,区分 String、Int、Option[String] 等。是否有一种方法可以抽象出这个可序列化的操作,这样如果我有很多 case class就像宠物一样,我可以简单地 运行 一个函数并得到正确的结果。
我在这里问了一个关于从案例 classes 中获取声明字段的相关问题,但似乎这种方式类型不安全,如果我添加更多自定义案例 [=41],可能会在以后产生问题=]es:
这通常是一件棘手的事情。此代码并不总是使用输出中的所有字段(例如 group_name
)并且字段名称并不总是与字符串中的名称匹配(例如 cat
与 cats
)
然而,有一些 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
处理,然后在最后提取。在这种情况下,我使用 flatten
将 List[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 类 之外,这对任何其他情况都不起作用 - 您必须使用反射。
我正在学习 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)
}
现在这里有一个重复的模式:
在 Pets 中,当我进行序列化时,我基本上遍历参数列表中的每个(键,值)对(group_name 除外)并执行以下操作:
键:value.serialize()
现在我不知道值的形式,它可以像示例中那样是一个选项。此外,假设我有很多 class 像宠物。在那种情况下,我将不得不在每个需要的参数上手动编写许多模式匹配,区分 String、Int、Option[String] 等。是否有一种方法可以抽象出这个可序列化的操作,这样如果我有很多 case class就像宠物一样,我可以简单地 运行 一个函数并得到正确的结果。
我在这里问了一个关于从案例 classes 中获取声明字段的相关问题,但似乎这种方式类型不安全,如果我添加更多自定义案例 [=41],可能会在以后产生问题=]es:
这通常是一件棘手的事情。此代码并不总是使用输出中的所有字段(例如 group_name
)并且字段名称并不总是与字符串中的名称匹配(例如 cat
与 cats
)
然而,有一些 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
处理,然后在最后提取。在这种情况下,我使用 flatten
将 List[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 类 之外,这对任何其他情况都不起作用 - 您必须使用反射。