35

Can anyone point me towards a simple example of Jackson serialization/deserialization with their Scala module for 2.10? I'm looking for reflection-based JSON not requiring field-by-field annotation or assignment and it seemed this could do that, but their documentation includes no examples.

If I have a case class:

case class Person(name:String, age:Int)
val person = Person("Fred", 65)

So from their github readme:

val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)

OK, now what...? How to I convert p to/from JSON?

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Greg
  • 10,696
  • 22
  • 68
  • 98

3 Answers3

52

Give this a shot:

val person = Person("fred", 25)
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)    

val out = new StringWriter
mapper.writeValue(out, person)
val json = out.toString()
println(json)

val person2 = mapper.readValue(json, classOf[Person])
println(person2)

EDIT

Just be sure to declare the Person class as top level as it will not work otherwise.

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • 1
    I just tried this with no luck -- the readValue call fails with: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize Class com.example.JacksonTest$Person$3 (of type local/anonymous) as a Bean. What am I missing? Can Person be declared exactly as: case class Person( name:String, age:Int ) ? – user48956 Oct 04 '13 at 01:23
  • @cmbaxter, is there a way to speed this up or this is the way they recommend? i.e. could you annotate to speed things up? – Blankman May 26 '14 at 14:37
  • Should that mapper be a singleton used throughout an application? – Blankman May 26 '14 at 14:38
  • Yes, one approach would be to set up a scala `object` and have an instance of the mapper tied to it. – cmbaxter May 26 '14 at 16:59
  • @Blankman, not sure what you are asking about in terms of speeding things up. Is the code execution not efficient enough for you or are you looking for ways to make the code less verbose? I've found the jackson-module-scala library to be very efficient when setup and used properly. – cmbaxter May 26 '14 at 17:02
  • @cmbaxter Sorry I thought this was the 'quick' way of implementing it but it may be a bit slower. What I meant to say is, would using annotations speed things up at all? i.e. is there a way to make this perform better (maybe at the cost of being more verbose). This deserializationg seems to use java reflection: https://github.com/FasterXML/jackson-module-scala/blob/master/src/test/scala/com/fasterxml/jackson/module/scala/deser/DeserializerTest.scala Your example is serialization I know, but deser seems a bit tricky when dealing with a Map. – Blankman May 26 '14 at 17:31
  • 2
    @Blankman, correct me if I'm wrong but runtime annotations in java are made available via reflection so using them won't help you avoid reflection. There are some macro based scala json frameworks out there that generate the ser/deser code via the macro and avoid reflection. I think both Play and Scala Pickling have macro based json handling and there are probably a few more. – cmbaxter May 26 '14 at 19:58
  • @cmbaxter I see, I'm not in a position to argue with that. I actually have to read up on macros as I haven't fully grasped the concept but your explanation hints to what they could be used for. thanks. – Blankman May 26 '14 at 21:32
  • -1 solution doesn't work :( and commenters have said as much, but post has not been updated to reflect where this solution even works since it is not working in the most basic case. – Dean Hiller Oct 10 '14 at 11:52
  • 2
    @DeanHiller, the solution works just fine. My guess is that user48956, the only one I can see as having an issue, did not declare the Person case class as a top level class and instead nested it inside of another class/object. You can't do this as I don't believe the things that need to be accessible (constructor) are. If you declare Person as top level things work just fine (or at least they did well over a year ago when I posted this answer). Not sure this really deserves a downvote... – cmbaxter Oct 10 '14 at 15:51
  • 1
    you can simply use `mapper.writeValueAsString(person)` if you want instant string output – Aivaras Sep 07 '16 at 11:01
13

Here's a complete example:

package com.example.samples

import org.junit.Test
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.context.annotation.Bean
import java.io.File
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import java.io.StringWriter

class JacksonTest {

  @Test
  @throws[Exception] def jacksonTest(): Unit = {

    //case class Person(var name: String = "", var age: Int = 0)
    //case class Person(@Bean var name: String, @Bean var age: Int)
    case class Person( name: String, age: Int )

    val person = Person("fred", 25)
    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)

    val out = new StringWriter
    mapper.writeValue(out, person)
    val json = out.toString()
    println(json)

    val person2 = mapper.readValue(json, classOf[Person])
    println(person2)
  }
}

However, this fails at mapper.readValue.

Here's my config:

<!-- Jackson libraries for JSON marshalling and unmarshalling -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.3</version>
</dependency>

<!-- Jackson module for scala object marshalling and unmarshalling -->
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-scala_2.10</artifactId>
    <version>2.2.2</version>
</dependency>

<!-- Scala Compiler -->
<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-compiler</artifactId>
    <version>2.10.2</version>
</dependency>

Any ideas why this fails? I can't see a difference with the working example.

erip
  • 16,374
  • 11
  • 66
  • 121
user48956
  • 14,850
  • 19
  • 93
  • 154
  • did you get this solution working at all in some way? – Dean Hiller Oct 10 '14 at 11:53
  • 4
    ok, I had to move my case class to be outside of my class and other functions so it is a full top level class instead and that fixed the issue for some reason. – Dean Hiller Oct 10 '14 at 11:59
  • 2
    The reason moving the case class to the outside is because when you define the case class in the test, it is actually an inner class. This means it needs to have a reference to the parent class and that is where things get complicated (there are similar issues when working with inner classes in java). The solution you found of putting it as a top level class is one solution. The other is putting the case class inside an object which makes it a static class and everything is fine. – Assaf Mendelson Mar 21 '17 at 15:07
-1

I have created a generic function to convert JSON String to Case Class/Object and Case Class/Object to JSON String.

SBT Dependencies required in build.sbt file:

name := "jackson-example"

scalaVersion := "2.12.11"

libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.10.1"
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.10.1"

JSON String to Case Class/Object

def fromJson[T](json: String)(implicit m: Manifest[T]): Option[T] = {
    Try {
      lazy val mapper = new ObjectMapper() with ScalaObjectMapper
      mapper.registerModule(DefaultScalaModule)
      mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
      mapper.readValue[T](json)
    } match {
      case Success(x) => Some(x)
      case Failure(err) => {
        logger.error("@@@@Got " + err.getMessage() + " while JSON to Object:--> " + json)
        None
      }
    }
  }

Case Class/Object to JSON String

def toJson[T](obj: T)(implicit m: Manifest[T]): Option[String] = {
    Try {
      lazy val mapper = new ObjectMapper() with ScalaObjectMapper
      mapper.registerModule(DefaultScalaModule)
      mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
      mapper.writeValueAsString(obj)
    } match {
      case Success(x) => Some(x)
      case Failure (err) => {
        logger.error("@@@@Got " + err.getMessage() + " while converting object  to JSON:--> " + obj)
        None
      }
    }
  }
Keshav Lodhi
  • 2,641
  • 2
  • 17
  • 23