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

如何在Tapir中实现Schema扁平化?无需额外定义case class

实现Tapir Schema扁平化DataBag泛型结构的方案

问题背景

我有如下泛型结构:

trait KeyType extends Product

case class DataBag[T <: KeyType](
   key: T,
   someData: String, 
   someMoreData: String
)

以及具体实现:

case class NameKey(firstName: String, lastName: String) extends KeyType // 修正:需继承KeyType以满足泛型约束
type PersonData = DataBag[NameKey]

想要定义Tapir Schema将结构扁平化,生成如下JSON输出,且避免额外定义case class:

{
   "firstName": "John",
   "lastName":  "Doe",
   "someData":  "foo",
   "someMoreData": "bar"
}

可行方案:自定义Tapir Schema合并字段

完全可以实现,核心思路是为DataBag[T]定义通用Schema,将key字段的子字段与DataBag自身的someData、someMoreData字段合并到顶层。

以下是惯用的简洁实现:

import sttp.tapir.Schema
import sttp.tapir.Schema.SProduct

// 为DataBag[T]定义通用扁平化Schema
implicit def dataBagSchema[T <: KeyType : Schema]: Schema[DataBag[T]] = {
  // 获取自动派生的基础Schema和key的Schema
  val baseDataBagSchema = Schema.derived[DataBag[T]]
  val keySchema = Schema[T]

  keySchema.schemaType match {
    // 处理key为Product类型的场景(符合需求的核心情况)
    case keyProduct: SProduct =>
      // 合并key的字段和DataBag除key外的字段
      val mergedFields = keyProduct.fields ++ baseDataBagSchema.fields.filterNot(_.name == "key")
      
      baseDataBagSchema.copy(
        schemaType = SProduct(
          fields = mergedFields,
          // 将DataBag实例转换为扁平化的键值对
          toProduct = { db =>
            val keyKeyValue = keySchema.toProduct(db.key).asInstanceOf[Map[String, Any]]
            val dataBagKeyValue = baseDataBagSchema.toProduct(db).asInstanceOf[Map[String, Any]] - "key"
            keyKeyValue ++ dataBagKeyValue
          },
          // 从扁平化的键值对还原DataBag实例
          fromProduct = { flatMap =>
            // 提取key字段的值并还原为T实例
            val keyValues = keyProduct.fields.map(f => flatMap(f.name))
            val key = keySchema.fromProduct(keyValues.toList).asInstanceOf[T]
            // 提取DataBag自身字段的值
            val someData = flatMap("someData").asInstanceOf[String]
            val someMoreData = flatMap("someMoreData").asInstanceOf[String]
            DataBag(key, someData, someMoreData)
          }
        )
      )
    // 非Product类型的key直接用基础Schema(可选,根据需求调整)
    case _ => baseDataBagSchema
  }
}

// 自动获取PersonData的扁平化Schema
implicit val personDataSchema: Schema[PersonData] = dataBagSchema[NameKey]

说明

  1. 修正了NameKey的继承关系,确保符合DataBag的泛型约束;
  2. 利用Tapir自动派生的基础Schema,通过模式匹配提取key的Product结构,实现字段合并;
  3. 重写toProduct和fromProduct方法,完成DataBag与扁平化结构的双向转换;
  4. 该方案具备通用性,所有继承KeyType的case class都能自动适配扁平化输出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:13:09