如何在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]
说明
- 修正了
NameKey的继承关系,确保符合DataBag的泛型约束; - 利用Tapir自动派生的基础Schema,通过模式匹配提取
key的Product结构,实现字段合并; - 重写
toProduct和fromProduct方法,完成DataBag与扁平化结构的双向转换; - 该方案具备通用性,所有继承
KeyType的case class都能自动适配扁平化输出。
内容的提问来源于stack exchange,提问作者Dima
相关产品推荐
相关产品推荐

