Json的案例类:关于如何避免重复模式匹配而不使用外部Json库的设计模式

Json的案例类:关于如何避免重复模式匹配而不使用外部Json库的设计模式,json,scala,pattern-matching,abstraction,case-class,Json,Scala,Pattern Matching,Abstraction,Case Class,我正在学习Scala案例类和设计模式。为此,我创建了一个示例,我认为在处理Json类型数据时,下面的示例很可能是一个场景。我知道有一些库可以做这些事情,但我正在手动学习Scala解决问题的方法,因为使用库不会帮助我学习 我想做的主要设计改进是抽象公共代码 假设我的代码库由许多case类组成,其中每个case类都是可序列化的: trait Base { def serialize(): String } trait Animal extends Base trait Mamm

我正在学习Scala案例类和设计模式。为此,我创建了一个示例,我认为在处理Json类型数据时,下面的示例很可能是一个场景。我知道有一些库可以做这些事情,但我正在手动学习Scala解决问题的方法,因为使用库不会帮助我学习

我想做的主要设计改进是抽象公共代码

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

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中,当我序列化时,我基本上会检查参数列表中的每个(键、值)对(组\名称除外),并执行以下操作:

    键:value.serialize()

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

    我在这里问了一个有关从cases类中获取声明字段的相关问题,但这种方法似乎不是类型安全的,如果我添加更多自定义case类,可能会在以后产生问题:


    一般来说,这是一件棘手的事情。此代码并不总是使用输出中的所有字段(例如
    group\u name
    ),并且字段的名称并不总是与字符串中的名称匹配(例如
    cat
    vs
    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类
    是不可变的,序列化的值不会更改,因此将其设置为名为
    serial
    的属性更有意义

    选项
    值最好在
    选项
    中使用
    映射进行处理,然后在最后提取。在本例中,我使用了
    plant
    列表[选项[String]]
    转换为
    列表[String]


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

    这里有一种通用方法,可以利用案例类是
    产品的事实,为案例类创建有限的序列化方法

    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\u 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}}
    
    在斯卡斯蒂:


    学习Scala时,这不是一个很好的起点,因为您正在将源代码符号名称放入数据值中。这涉及到使用在Scala(或者任何语言)中都很笨拙的反射。虽然这是在处理JSON时使用的,但正如您所说,现有的库可以完成这项工作。虽然这两种语言本身都不是很难,但在没有掌握语言其余部分的情况下学习它是一种受虐狂。谢谢,我将推迟学习运行时/编译时反射。我以前从未遇到过这个概念;博士这类问题的答案是a。请参阅。我知道你的问题说你不想使用它,但我还是推荐它酷,我喜欢映射而不是选项。我说这是利用选项monad属性,对吗?当单词“monad”出现时,我总是很紧张。我认为这是Scala库在处理像
    Option
    Try
    Future
    这样的集合对象时一致性的一个例子。
    map
    flatMap
    foreach
    等方法在这些类型上以“预期方式”运行,就像在正常收集(如
    List
    )中一样。但是我相信
    map
    (或者更确切地说
    flatMap
    )是monad的特征方法之一。