search:

Scala에서 Jackson 라이브러리 사용법

27 Nov 2020

본 포스팅은 두 가지 내용을 담고 있다.

  1. scala에서 Jackson을 사용하는 방법
  2. 그리고 jackson에 대한 기본적인 사용법들

검색해보면 다 나오는 내용이지만 나중에 쉽게 찾기 위해 정리해봤다

그리고 실제로 작동하는 repository도 하나 만들어봤다.

https://github.com/jason-heo/scala-jackson-example

검색해서 나오는 내용은 의존성이나 import가 누락되어 있어서 돌아가는 코드를 어떻게 작성해야하는지 헷갈릴 때가 있는데 이런 문제는 겪는 분들에게 도움이 될 것이라 생각한다.

목차

build.sbt에 의존성 추가하기

아래 내용만 추가하면 된다.

jsr310은 Date 관련 포맷 때문에 필요하다.

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.core" % "jackson-core" % "2.11.2",

  // JSR310 의 필요성: https://perfectacle.github.io/2018/01/16/jackson-local-date-time-serialize/
  "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.11.2",

  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.11.2"
)

Serialize 기본

writeValueAsString() 함수 하나면 class instance 변수를 json string으로 변환할 수 있다.

    case class Person(
                       name: String,
                       hasCat: Boolean
                     )

    val you: Person = Person(
      name = "Kim",
      hasCat = true
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)

    println(objectMapper.writeValueAsString(you))

출력 결과

{"name":"Kim","hasCat":true}

pretty print

writerWithDefaultPrettyPrinter()를 이용하면 이쁘게 출력할 수 있다.

    case class Person(
                       name: String,
                       hasCat: Boolean
                     )

    val you: Person = Person(
      name = "Kim",
      hasCat = true
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)

    println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(you))

출력 결과

{
  "name" : "Kim",
  "hasCat" : true
}

LocalDate Format 0 - Default Format

여기서부터 좀 어려워진다. LocalDate를 예로 들었지만, LocalDateTime에도 적용된다.

birthDate가 아주 이상하게 출력되었다.

  case class LocalDateFormat(
                              name: String,
                              hasCat: Boolean,

                              birthDate: LocalDate
                            )
  def localDateDefaultFormat(): Unit = {.
    val you = LocalDateFormat(
      name = "Kim",
      hasCat = true,
      birthDate = LocalDate.of(2000, 11, 23)
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)

    println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(you))
  }

출력 결과

{
  "name" : "Kim",
  "hasCat" : true,
  "birthDate" : {
    "year" : 2000,
    "month" : "NOVEMBER",
    "chronology" : {
      "calendarType" : "iso8601",
      "id" : "ISO"
    },
    "monthValue" : 11,
    "dayOfMonth" : 23,
    "era" : "CE",
    "dayOfYear" : 328,
    "dayOfWeek" : "THURSDAY",
    "leapYear" : true
  }
}

LocalDate Format 1 - JSR310 사용하기

JSR310을 사용하면 위의 결과보다는 포맷이 간단해진다.

  case class LocalDateTimeCustomFormat1(
                                         name: String,
                                         hasCat: Boolean,

                                         birthDate: LocalDate
                                       )

  // 출처1: https://www.baeldung.com/jackson-serialize-dates#iso-8601
  // 출처2: https://perfectacle.github.io/2018/01/16/jackson-local-date-time-serialize/
  def localDateCustomFormat1(): Unit = {
    val you = LocalDateTimeCustomFormat1(
      name = "Kim",
      hasCat = true,
      birthDate = LocalDate.of(2000, 11, 23)
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)
    objectMapper.registerModule(new JavaTimeModule)

    println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(you))
  }

출력 결과

{
  "name" : "Kim",
  "hasCat" : true,
  "birthDate" : [ 2000, 11, 23 ]
}

하지만, 여전히 Array 형식이라서 별로 안 좋다

LocalDate Format 2 - Array 대신 String으로 출력하기

  case class LocalDateCustomFormat2(
                                     name: String,
                                     hasCat: Boolean,

                                     birthDate: LocalDate
                                   )

  // 출처: 상동
  def localDateCustomFormat2(): Unit = {

    val you = LocalDateCustomFormat2(
      name = "Kim",
      hasCat = true,
      birthDate = LocalDate.of(2000, 11, 23)
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)
    objectMapper.registerModule(new JavaTimeModule)

    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) <=== 요게 추가되었다

    println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(you))
  }

출력 결과

{
  "name" : "Kim",
  "hasCat" : true,
  "birthDate" : "2000-11-23"
}

LocalDate Format 3 - custom format 지정하기

@JsonFormat annotation을 사용하면 format을 지정할 수 있다

  // @JsonFormat test
  case class LocalDateCustomFormat3(
                                     name: String,
                                     hasCat: Boolean,

                                     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd")
                                     birthDate: LocalDate
                                   )

  def localDateCustomFormat3(): Unit = {

    val you = LocalDateCustomFormat3(
      name = "Kim",
      hasCat = true,
      birthDate = LocalDate.of(2000, 11, 23)
    )

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)
    objectMapper.registerModule(new JavaTimeModule())

    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

    println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(you))
  }

Deserialize 1

readValue()를 이용하여 deserialize할 수 있다.

  case class PersonDeserialize1(
                                 name: String,
                                 hasCat: Boolean
                               )


  // 출처: https://stackoverflow.com/a/31867613/2930152
  def basicDeserialize1(): Unit = {

    val jsonStr = """
    {
      "name": "Kim",
      "hasCat": false
    }
    """

    val objectMapper = new ObjectMapper() with ScalaObjectMapper

    objectMapper.registerModule(DefaultScalaModule)

    println(objectMapper.readValue[PersonDeserialize1](jsonStr))
  }

출력 결과

PersonDeserialize1(Kim,false)

Deserialize 2

configure()를 이용하면 deserialize 관련된 많은 옵션을 지정할 수 있다.

두 가지 테스트해봤는데 내가 아직 잘 몰라서 그런지 생각했던 대로 돌아가지는 않았다.

필요할 때 다시 한번 테스트해봐야겠다.

  // configure test
  //   - json string에 필드가 누락된 경우 에러 테스트
  //   - json의 int를 Long Type으로 변환
  def basicDeserialize2(): Unit = {
    val jsonStr = """
    {
      "name": "Kim",
      "hasCat": false,
      "age": 1
    }
    """

    val objectMapper = new ObjectMapper()

      // 필드가 없는 경우 에러 테스트 -> 에러가 발생하지 않네;;
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)

      // true든 false든 deserialize가 잘 된다;;
      .configure(DeserializationFeature.USE_LONG_FOR_INTS, true)
      .registerModule(DefaultScalaModule)

    println(objectMapper.readValue(jsonStr, classOf[PersonDeserialize2]))
  }

출력 결과

PersonDeserialize2(Kim,false,null,1)

Deserialize 3 - Map으로 변환하기

class instance가 아닌 Map 타입으로도 변환할 수 있다.

  // Map으로 변환
  def basicDeserialize3(): Unit = {
    val jsonStr = """
    {
      "name": "Kim",
      "hasCat": false,
      "age": 1
    }
    """

    val objectMapper = new ObjectMapper()

    objectMapper.registerModule(DefaultScalaModule)

    println(objectMapper.readValue(jsonStr, classOf[Map[String, Any]]))
  }

출력 결과

Map(name -> Kim, hasCat -> false, age -> 1)

Deserialize 4 - LocalDate

필요해질 때 https://perfectacle.github.io/2018/01/15/jackson-local-date-time-deserialize/ 읽어보자.