Scala Play 2.4.11:无法反序列化以Case Class为键的Map,求原因
解决Play JSON解析
Map[自定义Case Class, Long]时的"No implicit format"异常 这个问题的核心原因很直白:Play JSON的默认Map序列化/反序列化只支持键为字符串(或能自动转为字符串的基础类型,比如Int、Long),而你的ItemCategory是自定义的case class,Play不知道怎么把它转换成JSON要求的字符串键,也不知道怎么把JSON的字符串键转回ItemCategory对象,所以就抛出了缺少隐式Format的异常。
解决方案:为自定义键类型实现KeyWrites和KeyReads
Play JSON专门提供了KeyWrites和KeyReads两个类型类,用来处理Map键的序列化(对象转字符串键)和反序列化(字符串键转对象)。你需要为ItemCategory实现这两个类型类,之后Play就能自动处理Map[ItemCategory, Long]了。
步骤1:根据你的JSON结构实现键的转换逻辑
首先得明确你的JSON中,itemCounters的键是什么格式。比如假设你的JSON长这样(键是Repository的env值):
{ "items": [ { "path": "/api/v1", "itemCounters": { "prod": 20, "dev": 8 } } ] }
那对应的KeyWrites和KeyReads可以这么写:
// 把ItemCategory转换成JSON字符串键:这里直接用repository的env值 implicit val itemCategoryKeyWrites: KeyWrites[ItemCategory] = KeyWrites(category => category.repository.env) // 把JSON字符串键转回ItemCategory:从字符串生成Repository和ItemCategory implicit val itemCategoryKeyReads: KeyReads[ItemCategory] = KeyReads { envStr => JsSuccess(ItemCategory(Repository(envStr))) }
如果你的JSON键是更复杂的格式(比如"repo-prod"这样的拼接字符串),只需要调整转换逻辑就行:
// 复杂键的示例:序列化时拼接字符串 implicit val itemCategoryKeyWrites: KeyWrites[ItemCategory] = KeyWrites(category => s"repo-${category.repository.env}") // 反序列化时拆分字符串 implicit val itemCategoryKeyReads: KeyReads[ItemCategory] = KeyReads { keyStr => keyStr.split("-") match { case Array("repo", env) => JsSuccess(ItemCategory(Repository(env))) case _ => JsError("Invalid key format for ItemCategory") } }
步骤2:保留原有的Format推导代码
实现完KeyWrites和KeyReads后,原来的自动推导代码就能正常工作了,Play会自动使用这两个类型类处理Map的键:
implicit lazy val repositoryFormat = Json.format[Repository] implicit lazy val itemCategoryFormat = Json.format[ItemCategory] implicit lazy val itemFormat = Json.format[Item] implicit lazy val itemsFormat = Json.format[Items] Json.parse(str).as[Items]
关键提醒
JSON的语法要求键必须是字符串,所以你的自定义类型作为Map键时,必须能双向映射到字符串——这个映射逻辑要和你的实际JSON结构完全匹配,否则会出现解析失败或者序列化结果不符合预期的问题。
内容的提问来源于stack exchange,提问作者Capacytron
相关产品推荐
相关产品推荐

