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

自定义json4s Serializer抛出MappingException未显示问题排查

问题描述

我在Play Framework应用的Tag.scala文件中定义了TagKey和TagValue,期望当JSON格式错误或传入的键/值不属于TagKey和TagValue时抛出MappingException。

预期的JSON格式为:

"tags": [{"key": "Key1", "value": "Value1"}, ....]

它会被转换为Option[Seq[Tag]]。

我希望为格式错误的JSON或无效键/值抛出错误,但MappingException似乎丢失了,Serializer返回空序列。另外,当不捕获NoSuchElementException直接转换时,仅得到org.json4s.package$MappingException: unknown error。

相关代码如下:

case class Tag(tagKey: TagKey, tagValue: TagValue)

sealed abstract class TagKey

object TagKey {
  case object Key1 extends TagKey
  case object Key2 extends TagKey
}

sealed abstract class TagValue

object TagValue {
  case object Value1 extends TagValue
  case object Value2 extends TagValue
}

object TagSerializer extends CustomSerializer[Tag](_ =>
  (
    {
      case JObject(List(("key", JString(k)), ("value", JString(v)))) =>
        try Tag(TagKey(k), TagValue(v))
        catch {
          case ex: java.util.NoSuchElementException =>
            throw new MappingException(s"Invalid key/value for tags. Error: $ex")
        }
      case unknownJson: Any => throw new MappingException("Invalid Json")
    },

    { case x: Tag =>
      JObject(List(("key", JString(x.tagKey)), ("value", JString(x.tagValue))))
    }
  )
)

我已将该TagSerializer添加到自定义body parser的formats中,序列化和反序列化功能正常,但异常无法正常显示,请问我哪里操作错误?


问题分析与解决

核心问题集中在枚举映射逻辑、序列化匹配严谨性以及Play反序列化模式三个方面,逐一修正即可:

1. 完善TagKey/TagValue的字符串映射逻辑

当前TagKey(k)和TagValue(v)没有实现字符串到枚举对象的匹配逻辑,会直接抛出未定义的异常,被json4s包装成模糊的unknown error。需要给枚举添加apply方法,明确处理匹配失败的场景:

sealed abstract class TagKey(val value: String)

object TagKey {
  case object Key1 extends TagKey("Key1")
  case object Key2 extends TagKey("Key2")

  def apply(str: String): TagKey = str match {
    case Key1.value => Key1
    case Key2.value => Key2
    case _ => throw new MappingException(s"无效的TagKey: $str,允许的值为${TagKey.values.map(_.value).mkString(", ")}")
  }

  val values: Seq[TagKey] = Seq(Key1, Key2)
}

sealed abstract class TagValue(val value: String)

object TagValue {
  case object Value1 extends TagValue("Value1")
  case object Value2 extends TagValue("Value2")

  def apply(str: String): TagValue = str match {
    case Value1.value => Value1
    case Value2.value => Value2
    case _ => throw new MappingException(s"无效的TagValue: $str,允许的值为${TagValue.values.map(_.value).mkString(", ")}")
  }

  val values: Seq[TagValue] = Seq(Value1, Value2)
}

2. 优化Serializer的反序列化匹配逻辑

原模式匹配JObject(List(("key", JString(k)), ("value", JString(v))))仅匹配键顺序固定且无额外字段的JSON对象,容错性极低。修改为灵活提取字段的逻辑:

object TagSerializer extends CustomSerializer[Tag](_ =>
  (
    {
      case obj: JObject =>
        val keyStr = (obj \ "key").extractOpt[String].getOrElse(throw new MappingException("tags数组中的对象缺少必填字段: key"))
        val valueStr = (obj \ "value").extractOpt[String].getOrElse(throw new MappingException("tags数组中的对象缺少必填字段: value"))
        Tag(TagKey(keyStr), TagValue(valueStr))
      case unknown => throw new MappingException(s"tags数组中的元素格式无效,期望是包含key和value的JSON对象,实际得到: $unknown")
    },
    { case x: Tag =>
      JObject(List(("key", JString(x.tagKey.value)), ("value", JString(x.tagValue.value))))
    }
  )
)

3. 启用Play反序列化的严格模式

反序列化Option[Seq[Tag]]时,默认逻辑会吞掉元素级别的异常,返回空序列或None。需要在自定义BodyParser的formats中开启严格解析:

import org.json4s.DefaultFormats
import org.json4s.ext.EnumNameSerializer

val customFormats = DefaultFormats + TagSerializer + new EnumNameSerializer(TagKey) + new EnumNameSerializer(TagValue)
val strictFormats = customFormats.withStrictParsing(true)

将strictFormats传入自定义BodyParser后,任何格式错误或无效键值都会抛出明确的MappingException,不会被静默忽略。


内容的提问来源于stack exchange,提问作者Sarthak Maheshwari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 23:10:04