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

spray.json反序列化异常:如何将空JSON转为JsString适配Either类型

解决spray.json反序列化空JSON对象为Right("")的问题

问题核心

你的Record类中payload用Either[PayloadObject, String]实现,spray.json默认处理Either的逻辑是先尝试反序列化左边的PayloadObject,失败再转右边的String。但当JSON里的payload是{}时,要么会直接反序列化成Left(空PayloadObject)(如果PayloadObject支持空字段),要么直接抛出反序列化异常,根本不会走到Right("")的分支。要实现需求,必须自定义JSON格式化器。

具体解决方案

1. 先确保PayloadObject的基础格式化器可用

假设你已经为PayloadObject定义了默认的JSON格式化器,比如:

import spray.json._

case class PayloadObject(field1: Option[String], field2: Option[Int])

object PayloadObjectJsonProtocol extends DefaultJsonProtocol {
  implicit val payloadObjectFormat: RootJsonFormat[PayloadObject] = jsonFormat2(PayloadObject)
}

2. 为Record自定义JSON格式化器

手动控制payload字段的反序列化逻辑,把空JSON对象{}直接映射为Right(""):

import spray.json._

case class Record(id: String, version: Long, payload: Either[PayloadObject, String])

object RecordJsonProtocol extends PayloadObjectJsonProtocol {
  implicit val recordFormat: RootJsonFormat[Record] = new RootJsonFormat[Record] {
    // 序列化逻辑:按正常Either规则输出JSON
    override def write(record: Record): JsValue = {
      val payloadJs = record.payload match {
        case Left(obj) => obj.toJson
        case Right(str) => JsString(str)
      }
      JsObject(
        "id" -> JsString(record.id),
        "version" -> JsNumber(record.version),
        "payload" -> payloadJs
      )
    }

    // 反序列化逻辑:针对性处理空对象
    override def read(json: JsValue): Record = json match {
      case JsObject(fields) =>
        val id = fields("id").convertTo[String]
        val version = fields("version").convertTo[Long]
        val payloadJs = fields("payload")
        
        val payload = payloadJs match {
          // 遇到空JSON对象{},直接返回Right("")
          case JsObject(objFields) if objFields.isEmpty => Right("")
          // 非空对象先尝试转PayloadObject,成功则返回Left
          case js =>
            try {
              Left(js.convertTo[PayloadObject])
            } catch {
              // 转PayloadObject失败时,尝试转String;其他情况也统一返回空字符串(可按需调整)
              case _: DeserializationException =>
                js match {
                  case JsString(str) => Right(str)
                  case _ => Right("")
                }
            }
        }
        Record(id, version, payload)
      case _ => throw DeserializationException("Expected JsObject for Record")
    }
  }
}

3. 单元测试验证

现在测试"payload":{}的场景就能通过了:

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import spray.json._

class RecordSpec extends AnyFlatSpec with Matchers {
  import RecordJsonProtocol._

  "Record" should "deserialize empty payload object to Right(\"\")" in {
    val jsonStr = """{"id": "123", "version": 1, "payload": {}}"""
    val record = jsonStr.parseJson.convertTo[Record]
    record.payload shouldBe Right("")
  }

  it should "deserialize valid PayloadObject to Left" in {
    val jsonStr = """{"id": "456", "version": 2, "payload": {"field1": "test", "field2": 10}}"""
    val record = jsonStr.parseJson.convertTo[Record]
    record.payload shouldBe Left(PayloadObject(Some("test"), Some(10)))
  }

  it should "deserialize string payload to Right" in {
    val jsonStr = """{"id": "789", "version": 3, "payload": "hello"}"""
    val record = jsonStr.parseJson.convertTo[Record]
    record.payload shouldBe Right("hello")
  }
}

关键说明

  • 自定义格式化器中优先判断payload是否为空JsObject,直接命中你需要的Right("")分支
  • 非空对象依然保留原有的Either逻辑,先尝试转PayloadObject,失败再转String
  • 序列化逻辑保持正常输出,确保写入JSON的格式符合预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 00:20:44