使用 Jackson 对 Scala 案例进行(反)序列化 Class

Using Jackson to (De)-serialize a Scala Case Class

我使用 Jackson 测试了 Scala 案例 class 的序列化。

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的Javaclass时,抛出异常:

[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 案例 classes?

Jackson 期望您的 class 是一个 JavaBean,这意味着它期望 class 每个 属性 都有一个 getX() and/or setX() .

选项 1

您可以使用注解 BeanProperty.

在 Scala 中创建 JavaBean classes

例子

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 classes,这可能是更好的方法。我没有使用它,因为我只是使用内置的 Json 库来玩。

找到了适用于 jackson 和 scala 的解决方案 classes.

我为 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 在我的案例 class 中注释字段。

这是我的案例 class 的样子:

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。

根据 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
  • 我创建了一个通用函数来转换 JSON String to Case Class/ObjectCase Class/Object to JSON String

  • 请找到我使用泛型提供的有效且详细的答案 here

使用 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)

Jackson 的解决方案更加冗长。请参阅此 post 以了解有关此方法的更多信息。

如果您使用的是 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);
  }
}

然后序列化/反序列化普通大小写 类 和集合类型将按预期工作。