You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala 2.13:为多类型值创建通用Case Class

解决Scala Case Class兼容多类型Value字段的问题

问题描述

给定如下JSON Schema片段:

"elements": {
      "{element1Name}": {
        "value": "<p>The HTML content of the element.</p>",
        ":type": "text/html",
        "variations": {
          "{variation1Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{variation2Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{elementNName}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          }
        }
      },
      "{element2Name}": {
        "value": "<p>The HTML content of the element.</p>",
        ":type": "text/html",
        "variations": {
          "{variation1Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{variation2Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{elementNName}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          }
        }
      },
      "{elementNName}": {
        "value": "<p>The HTML content of the element.</p>",
        ":type": "text/html",
        "variations": {
          "{variation1Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{variation2Name}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          },
          "{elementNName}": {
            "value": "<p>The HTML content of the element.</p>",
            ":type": "text/html"
          }
        }
      }
    }

当前已实现的Scala Case Class结构如下:

case class AssetAPIEntity(
  value: String,
  @key(":type") `type`: Option[String] = None,
)
case class AssetAPIElements(
  value: String,
  @key(":type") `type`: Option[String] = None,
  variations: Map[String, AssetAPIEntity]
)
case class AssetAPIData(
  title: Option[String] = None,
  description: Option[String] = None,
  @key("cq:Model") cqModel: String,
  elements: Map[String, AssetAPIElements]
)

需要修改value字段以支持以下6种类型:

  • 单个数字
  • 单个布尔值
  • 单个字符串
  • 数字序列
  • 布尔值序列
  • 字符串序列

两种可行方案

方案1:代数数据类型(ADT)

这是Scala处理多类型场景的类型安全方案,适合需要严格类型检查的场景。

定义Value类型的ADT

// 密封特质,限制实现范围
sealed trait Value

// 单值实现
case class SingleString(value: String) extends Value
case class SingleNumber(value: Double) extends Value // 用Double兼容整数和浮点数
case class SingleBoolean(value: Boolean) extends Value

// 序列值实现
case class MultiString(value: Seq[String]) extends Value
case class MultiNumber(value: Seq[Double]) extends Value
case class MultiBoolean(value: Seq[Boolean]) extends Value

修改原有Case Class

将value字段的类型替换为Value:

import io.circe._
import io.circe.generic.semiauto._

case class AssetAPIEntity(
  value: Value,
  @key(":type") `type`: Option[String] = None,
)

case class AssetAPIElements(
  value: Value,
  @key(":type") `type`: Option[String] = None,
  variations: Map[String, AssetAPIEntity]
)

case class AssetAPIData(
  title: Option[String] = None,
  description: Option[String] = None,
  @key("cq:Model") cqModel: String,
  elements: Map[String, AssetAPIElements]
)

实现JSON编解码器(以Circe为例)

需要为Value类型手动编写编解码器,确保JSON和Scala类型正确转换:

// 编码器
implicit val valueEncoder: Encoder[Value] = Encoder.instance {
  case SingleString(v) => Json.fromString(v)
  case SingleNumber(v) => Json.fromDoubleOrNull(v)
  case SingleBoolean(v) => Json.fromBoolean(v)
  case MultiString(v) => Json.fromValues(v.map(Json.fromString))
  case MultiNumber(v) => Json.fromValues(v.map(Json.fromDoubleOrNull))
  case MultiBoolean(v) => Json.fromValues(v.map(Json.fromBoolean))
}

// 解码器
implicit val valueDecoder: Decoder[Value] = Decoder.instance { c =>
  c.as[String].map(SingleString) orElse
  c.as[Double].map(SingleNumber) orElse
  c.as[Boolean].map(SingleBoolean) orElse
  c.as[Seq[String]].map(MultiString) orElse
  c.as[Seq[Double]].map(MultiNumber) orElse
  c.as[Seq[Boolean]].map(MultiBoolean)
}

// 自动生成其他类的编解码器
implicit val assetAPIEntityCodec: Codec[AssetAPIEntity] = deriveCodec
implicit val assetAPIElementsCodec: Codec[AssetAPIElements] = deriveCodec
implicit val assetAPIDataCodec: Codec[AssetAPIData] = deriveCodec

方案2:Scala 3联合类型

如果使用Scala 3,可以直接用联合类型简化代码,无需额外定义ADT。

定义联合类型别名

import io.circe._
import io.circe.generic.semiauto._

// 定义支持的所有类型的联合别名
type ValueType = String | Double | Boolean | Seq[String] | Seq[Double] | Seq[Boolean]

修改原有Case Class

将value字段类型替换为ValueType:

case class AssetAPIEntity(
  value: ValueType,
  @key(":type") `type`: Option[String] = None,
)

case class AssetAPIElements(
  value: ValueType,
  @key(":type") `type`: Option[String] = None,
  variations: Map[String, AssetAPIEntity]
)

case class AssetAPIData(
  title: Option[String] = None,
  description: Option[String] = None,
  @key("cq:Model") cqModel: String,
  elements: Map[String, AssetAPIElements]
)

实现JSON编解码器(以Circe为例)

// 编码器
implicit val valueTypeEncoder: Encoder[ValueType] = Encoder.instance {
  case s: String => Json.fromString(s)
  case n: Double => Json.fromDoubleOrNull(n)
  case b: Boolean => Json.fromBoolean(b)
  case ss: Seq[String] => Json.fromValues(ss.map(Json.fromString))
  case ns: Seq[Double] => Json.fromValues(ns.map(Json.fromDoubleOrNull))
  case bs: Seq[Boolean] => Json.fromValues(bs.map(Json.fromBoolean))
}

// 解码器
implicit val valueTypeDecoder: Decoder[ValueType] = Decoder.instance { c =>
  c.as[String].widen[ValueType] orElse
  c.as[Double].widen[ValueType] orElse
  c.as[Boolean].widen[ValueType] orElse
  c.as[Seq[String]].widen[ValueType] orElse
  c.as[Seq[Double]].widen[ValueType] orElse
  c.as[Seq[Boolean]].widen[ValueType]
}

// 自动生成其他类的编解码器
implicit val assetAPIEntityCodec: Codec[AssetAPIEntity] = deriveCodec
implicit val assetAPIElementsCodec: Codec[AssetAPIElements] = deriveCodec
implicit val assetAPIDataCodec: Codec[AssetAPIData] = deriveCodec

方案选择建议

  • ADT方案:类型安全,模式匹配时编译器会强制检查所有分支,避免遗漏;便于后续扩展新类型,适合大型项目或对类型严格要求的场景。
  • 联合类型方案:代码更简洁,无需额外定义case class,但类型安全性稍弱(模式匹配无强制分支检查),仅适用于Scala 3环境,适合小型项目或快速开发场景。

内容的提问来源于stack exchange,提问作者Shivam Sahil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 05:15:48