使用 Jackson 来(反)序列化 Scala 案例类
Posted
技术标签:
【中文标题】使用 Jackson 来(反)序列化 Scala 案例类【英文标题】:Using Jackson to (De)-serialize a Scala Case Class 【发布时间】:2015-03-31 23:29:35 【问题描述】:我使用 Jackson 测试了 Scala 案例类的序列化。
DeserializeTest.java
public static void main(String[] args) throws Exception // being lazy to catch-all
final ObjectMapper mapper = new ObjectMapper();
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
mapper.writeValue(stream, p.Foo.personInstance());
System.out.println("result:" + stream.toString());
Foo.scala
object Foo
case class Person(name: String, age: Int, hobbies: Option[String])
val personInstance = Person("foo", 555, Some("things"))
val PERSON_JSON = """ "name": "Foo", "age": 555 """
当我运行上面main
的Java类时,抛出了异常:
[error] Exception in thread "main" org.codehaus.jackson.map.JsonMappingException:
No serializer found for class p.Foo$Person and no properties discovered
to create BeanSerializer (to avoid exception,
disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
如何(反)序列化 Scala 案例类?
【问题讨论】:
Jackson 有几个很好的 Scala 包装器;你至少应该考虑使用一个。 Play-json 恰好是我使用的。好的 (IMO) 使用基于类型类的反序列化/序列化,您可以在其中将成员添加到案例类的伴随对象中,以告诉它如何来回映射到 JSON,并为此提供很好的习惯用法。 【参考方案1】:Jackson 期望您的类是 JavaBean,这意味着它期望该类的每个属性都有一个 getX() 和/或 setX()。
选项 1
您可以在 Scala 中使用注解 BeanProperty 创建 JavaBean 类。
例子
case class Person(
@BeanProperty val name: String,
@BeanProperty val age: Int,
@BeanProperty val hobbies: Option[String]
)
在这种情况下,val 意味着只定义了一个 getter。如果你想要反序列化的设置器,你将属性定义为 var。
选项 2
虽然选项 1 会起作用,但如果你真的想使用 Jackson,那么有一些包装器可以让它处理像 FasterXML's scala module 这样的 Scala 类,这可能是一种更好的方法。我没有使用它,因为我一直在使用内置的 Json 库来玩。
【讨论】:
对于选项 1,您需要在案例类中添加一个无参数构造函数,如下所示: def this() = this("",0,None)
因为距离最初的问题已经过去五年了,我应该补充一点,答案可能有以前工作过,但我现在将 Scala 2.13.4
和 jackson-module-scala 2.11.3
与 jackson-dataformat-csv 2.11.3
一起使用,这是必要的,否则我会收到消息 "Cannot construct instance of `Person` (no Creators, like default constructor, exist)"
【参考方案2】:
找到了一个适用于 jackson 和 scala 案例类的解决方案。
我为 jackson 使用了一个 scala 模块 - jackson-module-scala。
libraryDependencies ++= Seq(
"com.fasterxml.jackson.core" % "jackson-databind" % "2.5.3",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.2.2"
)
我必须使用 @JsonProperty 注释我的案例类中的字段。
这是我的案例类的样子:
case class Person(@JsonProperty("FName") FName: String, @JsonProperty("LName") LName: String)
这就是我反序列化的方式:
val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
val str = """"FName":"Mad", "LName": "Max""""
val name:Person = objectMapper.readValue[Person](str)
序列化更容易:
val out = new ByteArrayOutputStream()
objectMapper.writeValue(out, name)
val json = out.toString
想澄清一下我正在使用
com.fasterxml.jackson.databind.ObjectMapper
在这个问题中,他似乎在使用
org.codehaus.jackson.map.ObjectMapper
这不适用于 ScalaObjectMapper。
【讨论】:
您的解决方案没有涉及“爱好:Option[String]”属性,我认为他的部分问题在于将 json String 属性作为 Option[String] 处理。 Jackson 需要知道如何将 Option[String] 解释为 Some[String] 或 None 几点注意事项:1)@JsonProperty 在案例类尤其是不必要的。当属性名称与字段名称相同时。 2) 您应该尝试使用与 jackson-core 相同版本的 jackson-module-scala。 3) Jackson 2.0 被重新打包为 com.fasterxml,但在其他方面与 Jackson 1.0 一样好(如果不是更好的话)。【参考方案3】:根据 Priyank Desai 的回答,我创建了一个通用函数来将 json string
转换为 case class
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
def jsonToType[T](json:String)(implicit m: Manifest[T]) :T =
val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
objectMapper.readValue[T](json)
用法:
case class Person(@JsonProperty("name") Name:String, @JsonProperty("age") Age:Int)
val personName = jsonToType[Person](jsonString).name
【讨论】:
清晰有效的答案。只是一个小错字 - 参数name
而不是 Name
【参考方案4】:
我创建了一个通用函数来转换JSON String to Case Class/Object
和Case Class/Object to JSON String
。
请找到我使用泛型 here 提供的有效且详细的答案。
【讨论】:
【参考方案5】:使用 upickle 反序列化对象比使用 Jackson 容易得多。这是一个例子:
case class City(name: String, funActivity: String, latitude: Double)
implicit val cityRW = upickle.default.macroRW[City]
val str = """"name":"Barcelona","funActivity":"Eat tapas","latitude":41.39"""
val barcelona = upickle.default.read[City](str)
杰克逊的解决方案更加冗长。请参阅此post 以了解有关此方法的更多信息。
【讨论】:
【参考方案6】:如果您使用的是 Spring MVC,请使用如下配置:
import java.util.List
@Configuration
@EnableWebMvc
class WebConfig extends WebMvcConfigurer
override def configureMessageConverters(converters: List[HttpMessageConverter[_]]): Unit =
val conv = new MappingJackson2HttpMessageConverter();
val mapper = JsonMapper.builder()
.addModule(DefaultScalaModule)
.build();
conv.setObjectMapper(mapper);
converters.add(conv);
然后序列化/反序列化普通案例类和集合类型将按预期工作。
【讨论】:
以上是关于使用 Jackson 来(反)序列化 Scala 案例类的主要内容,如果未能解决你的问题,请参考以下文章