自定义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

