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

Spray-Json序列化None为null:Akka-HTTP迁移API兼容问题

解决Spray-Json序列化Option[Error]时保留"error":null的问题

我之前做REST API迁移的时候也碰到过一模一样的问题——Spray-Json默认会把Option类型的None字段直接省略,但旧API要求必须保留error字段并设为null才能兼容。其实有两种靠谱的解决办法,看你需求选:

方法一:为Response类自定义JsonFormat(精准控制)

如果只有Response这个类需要保留error字段,最稳妥的方式是手动实现它的RootJsonFormat,强制序列化时输出error字段,None就对应JsNull。

import spray.json._

// 先定义你的Result和Error样例类
case class Result(data: String)
case class Error(message: String)
case class Response(result: Result, error: Option[Error])

// 自定义Json协议
object ApiJsonProtocol extends DefaultJsonProtocol {
  // 先给Result和Error生成默认的Format
  implicit val resultFormat: RootJsonFormat[Result] = jsonFormat1(Result)
  implicit val errorFormat: RootJsonFormat[Error] = jsonFormat1(Error)
  
  // 手动实现Response的Format
  implicit val responseFormat: RootJsonFormat[Response] = new RootJsonFormat[Response] {
    override def write(response: Response): JsValue = JsObject(
      "result" -> response.result.toJson,
      // 不管error是Some还是None,都写入字段,None时用JsNull
      "error" -> response.error.map(_.toJson).getOrElse(JsNull)
    )
    
    override def read(json: JsValue): Response = {
      val jsObj = json.asJsObject
      Response(
        jsObj.fields("result").convertTo[Result],
        // 反序列化时处理error字段:存在则转成Option[Error],不存在则设为None
        jsObj.fields.get("error").flatMap {
          case JsNull => None
          case jsVal => Some(jsVal.convertTo[Error])
        }
      )
    }
  }
}

这样序列化Response(Result("test"), None)的时候,就会得到你要的结果:

{ "result": { "data": "test" }, "error": null }

方法二:全局修改Option的序列化规则(批量生效)

如果你的整个项目都需要让所有Option类型的None都输出为null(而不是省略),可以重写默认协议里的optionFormat:

import spray.json._

// 自定义默认协议,覆盖Option的Format
object GlobalCustomJsonProtocol extends DefaultJsonProtocol {
  override protected def optionFormat[T: JsonFormat]: JsonFormat[Option[T]] = new JsonFormat[Option[T]] {
    override def write(option: Option[T]): JsValue = option match {
      case Some(value) => value.toJson
      case None => JsNull // None时输出JsNull,而不是省略字段
    }
    
    override def read(json: JsValue): Option[T] = json match {
      case JsNull => None
      case jsValue => Some(jsValue.convertTo[T])
    }
  }
}

之后在需要序列化的地方导入GlobalCustomJsonProtocol._,而不是默认的DefaultJsonProtocol._,所有Option字段的None都会被序列化为null。

注意:这个方法会影响所有使用该协议的Option字段,如果其他地方需要保留默认省略None的行为,就别用这个,选方法一更合适。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 22:48:11