如何在Circe中为JSON字段设置别名并实现解码?
在Circe中为Scala样例类实现别名字段解码的最简方式
针对你的场景——需要让Circe解码器同时识别样例类字段的原名和指定别名,且保留其余100个可选字段的自动推导——最简实现方式是在自动推导的基础解码器之上,添加JSON字段名的前置转换逻辑,将别名字段重命名为原字段名后再交给基础解码器处理。
实现代码
import io.circe._, io.circe.generic.semiauto._ case class NN( name1: Option[String], someRandomName: String // 其余100个可选字段 ) object NN { // 自动推导基础解码器,处理所有字段的默认解码逻辑 private val baseDecoder: Decoder[NN] = deriveDecoder[NN] // 为解码器添加别名处理逻辑:将JSON中的别名字段重命名为原字段名 implicit val decoder: Decoder[NN] = baseDecoder.mapJson { json => json.asObject.fold(json) { obj => obj.get("someArbitraryName") match { case Some(value) => // 移除别名字段,添加原名字段 obj.remove("someArbitraryName").add("someRandomName", value).asJson case None => json } } } }
核心逻辑说明
- 自动推导基础解码器:
deriveDecoder[NN]会自动为所有字段生成解码逻辑,包括那100个可选字段,无需手动编写。 - 别名转换:
mapJson方法允许我们在解码前修改JSON结构——检查是否存在someArbitraryName字段,若存在则将其替换为someRandomName,确保基础解码器能正确识别。 - 无侵入性:该方式完全保留了其他字段的自动推导逻辑,不需要额外修改其他字段的代码。
扩展优化(若使用自定义@alias注解)
如果你的@alias是自定义注解,还可以通过反射读取注解值,实现通用的别名处理逻辑,避免硬编码别名:
import scala.reflect.runtime.universe._ // 假设自定义@alias注解 class alias(value: String) extends scala.annotation.StaticAnnotation // 通用工具方法:获取字段的别名 def getAlias(fieldName: String, cls: Class[_]): Option[String] = { cls.getDeclaredFields.find(_.getName == fieldName) .flatMap(f => Option(f.getAnnotation(classOf[alias])).map(_.value())) } // 修改解码器逻辑 implicit val decoder: Decoder[NN] = baseDecoder.mapJson { json => json.asObject.fold(json) { obj => getAlias("someRandomName", classOf[NN]) match { case Some(aliasName) => obj.get(aliasName).fold(json) { value => obj.remove(aliasName).add("someRandomName", value).asJson } case None => json } } }
测试验证
// 测试原名字段的JSON val json1 = Json.obj("someRandomName" -> Json.fromString("abc"), "name1" -> Json.fromString("test")) assert(json1.as[NN].right.get == NN(Some("test"), "abc")) // 测试别名字段的JSON val json2 = Json.obj("someArbitraryName" -> Json.fromString("abc"), "name1" -> Json.Null) assert(json2.as[NN].right.get == NN(None, "abc"))
内容的提问来源于stack exchange,提问作者tribbloid
相关产品推荐
相关产品推荐

