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
相关产品推荐
相关产品推荐

