16

I tested out the serialization of a Scala case class using Jackson.

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 } """
}

When I ran the above main of the Java class, an exception was thrown:

[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) )

How can I (de)-serialize Scala case classes?

Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • 1
    There are several good Scala wrappers for Jackson; you should at least consider using one. Play-json happens to be what I use. The good ones (IMO) use typeclass-based de/serialization, where you add members to your case-class's companion object to tell it how to map back and forth to JSON, and provide nice idioms for doing so. – Ed Staub Feb 02 '15 at 04:53

6 Answers6

26

Jackson is expecting your class to be a JavaBean, which means its expects the class to have a getX() and/or setX() for every property.

Option 1

You can create JavaBean classes in Scala using the annotation BeanProperty.

Example

case class Person(
   @BeanProperty val name: String, 
   @BeanProperty val age: Int, 
   @BeanProperty val hobbies: Option[String]
)

In this case a val will mean only a getter is defined. If you want setters for deserialization you defined the properties as var.

Option 2

While option 1 will work, if you really want to use Jackson there are wrappers that allow it to deal with Scala classes like FasterXML's scala module which might be a better approach. I haven't used it as I've just been using the Json library built in to play.

Lionel Port
  • 3,492
  • 23
  • 26
  • 1
    For option 1 you will need to add a no-args constructor to the case class, like this: `{ def this() = this("",0,None) } ` Since it's been five years since the original question, I should add that the answer may have worked before, but I am now using Scala `2.13.4` and `jackson-module-scala 2.11.3` with `jackson-dataformat-csv 2.11.3` and this is necessary otherwise I get the message `"Cannot construct instance of \`Person\` (no Creators, like default constructor, exist)"` – Anthony Holland Nov 27 '20 at 05:02
19

Found a solution that works with jackson and scala case classes.

I used a scala module for jackson - jackson-module-scala.

libraryDependencies ++= Seq(
 "com.fasterxml.jackson.core" % "jackson-databind" % "2.5.3",
 "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.2.2"
)

I had to annotate fields in my case class with @JsonProperty.

This is what my case class looks like:

case class Person(@JsonProperty("FName") FName: String, @JsonProperty("LName") LName: String)

And this is how I deserialize:

val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
val str = """{"FName":"Mad", "LName": "Max"}"""
val name:Person = objectMapper.readValue[Person](str)

Serialization is easier:

val out = new ByteArrayOutputStream()
objectMapper.writeValue(out, name)
val json = out.toString

Would like to clarify that I am using

com.fasterxml.jackson.databind.ObjectMapper

In the question, it seems he is using

org.codehaus.jackson.map.ObjectMapper 

which won't work with ScalaObjectMapper.

Priyank Desai
  • 3,693
  • 1
  • 27
  • 17
  • 1
    your solution didn't touch on the "hobbies: Option[String]" attribute and I think part of his problem was with handling a json String attribute as an Option[String]. Jackson needs to know how to interpret an Option[String] as a Some[String] or None – Andrew Norman Feb 19 '16 at 20:45
  • 4
    Few notes: 1) @JsonProperty is unnecessary on case classes esp. when the property name is the same as the field name. 2) You should try to use the same version of jackson-module-scala as being used with jackson-core. 3) Jackson 2.0 was repackaged as com.fasterxml but otherwise works as good as (if not a lot better) as Jackson 1.0. – Nate Feb 22 '16 at 03:00
4

Based on Priyank Desai's answer I have created a generic function to convert json string to 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)
}

Usage:

case class Person(@JsonProperty("name") Name:String, @JsonProperty("age") Age:Int)

val personName = jsonToType[Person](jsonString).name
faisal00813
  • 400
  • 5
  • 12
0

Deserializing objects is way easier with upickle than Jackson. Here's an example:

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)

The Jackson solutions are way more verbose. See this post to learn more about this approach.

Powers
  • 18,150
  • 10
  • 103
  • 108
-1
  • I have created a generic function to convert JSON String to Case Class/Object and Case Class/Object to JSON String.

  • Please find a working and detailed answer which I have provided using generics here.

Keshav Lodhi
  • 2,641
  • 2
  • 17
  • 23
-1

If you are using Spring MVC, use a configuration like this:

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);
  }
}

Then serializing / deserializing plain case classes and Collection types will work as expected.

gus
  • 171
  • 2
  • 7