为具有抽象成员的案例类构建 JSON 格式



我正在使用Play框架并尝试为具有抽象成员的类构建JSON验证器。如下所示,数据源类是我尝试验证格式的基类。

// SourceTypeConfig Trait.
trait SourceTypeConfig
  final case class RDBMSConfig(...) extends SourceTypeConfig
  object RDBMSConfig { implicit val fmt = Json.format[RDBMSConfig] }
  final case class DirectoryConfig(
      path: String,
      pathType: String // Local, gcloud, azure, aws, etc.
  ) extends SourceTypeConfig
  object DirectoryConfig { implicit val fmt = Json.format[DirectoryConfig] }
// FormatConfig trait.
trait FormatConfig
  final case class SQLConfig(...) extends FormatConfig
  object SQLConfig { implicit val fmt = Json.format[SQLConfig]}
  final case class CSVConfig(
      header: String,
      inferSchema: String,
      delimiter: String
  ) extends FormatConfig
  object CSVConfig { implicit val fmt = Json.format[CSVConfig]}

// DataSource base class.
case class DataSource(
      name: String,
      sourceType: String,
      sourceTypeConfig: SourceTypeConfig,
      format: String,
      formatConfig: FormatConfig
  )

我希望完成什么:

val input: JsValue = Json.parse(
      """
        {
        "name" : "test1",
        "sourceType" : "directory",
        "sourceTypeConfig" : {"path" : "gs://test/path", "pathType" "google"},
        "format" : "csv",
        "formatConfig" : {"header" : "yes", "inferSchema" : "yes",  "delimiter" :  "|"}
        }
      """
    )
    val inputResult = input.validate[DataSource]

我正在努力解决的是构建数据源对象并定义其读取/写入/格式。我希望它包含基于sourceTypeformat值的匹配项,该值指示它指向关联的sourceTypeConfigformatConfig的格式,以便它可以解析出 JSON。

我没有在DataSource级别构建解析器,而是在SourceConfigFormatConfig级别定义了解析器,如下所示。

sealed trait SourceConfig{val sourceType: String}
object SourceConfig{
  implicit val fmt = new Format[SourceConfig] {
    def reads(json: JsValue): JsResult[SourceConfig] = {
      def from(sourceType: String, data: JsObject): JsResult[SourceConfig] = sourceType match {
        case "RDBMS"     => Json.fromJson[RDBMSConfig](data)(RDBMSConfig.fmt)
        case "directory" => Json.fromJson[DirectoryConfig](data)(DirectoryConfig.fmt)
        case _           => JsError(s"Unknown source type: '$sourceType'")
      }
      for {
        sourceType <- (json  "sourceType").validate[String]
        data       <- json.validate[JsObject]
        result     <- from(sourceType, data)
      } yield result
    }
    def writes(source: SourceConfig): JsValue =
      source match {
        case b: RDBMSConfig     => Json.toJson(b)(RDBMSConfig.fmt)
        case b: DirectoryConfig => Json.toJson(b)(DirectoryConfig.fmt)
      }
  }
}

然后,DataSource可以简单地定义为:

object DataSource { implicit val fmt = Json.format[DataSource] }

另一种选择是使用play-json-derived-codecs库:

libraryDependencies += "org.julienrf" %% "play-json-derived-codecs" % "4.0.0"
import julienrf.json.derived.flat
implicit val format1: OFormat[RDBMSConfig] = Json.format[RDBMSConfig]
implicit val format2: OFormat[DirectoryConfig] = Json.format[DirectoryConfig]
implicit val format3: OFormat[SourceTypeConfig] = flat.oformat((__  "sourceType").format[String])

最新更新