3

I'm trying to serialize a case class with optional value-class field to JSON using json4s. So far, I'm not able to get the optional value-class field rendered correctly (see the snippet below with examples).

I tried json-native and json-jackson libraries, results are identical.

Here's a short self-contained test

import org.json4s.DefaultFormats
import org.scalatest.FunSuite
import org.json4s.native.Serialization._

class JsonConversionsTest extends FunSuite {
  implicit val jsonFormats = DefaultFormats

  test("optional value-class instance conversion") {
    val json = writePretty(Foo(Option(Id(123)), "foo-name", Option("foo-opt"), Id(321)))

    val actual =
      """
        |{
        |  "id":{
        |    "value":123
        |  },
        |  "name":"foo-name",
        |  "optField":"foo-opt",
        |  "nonOptId":321
        |}
        |""".stripMargin.trim

    assert(json === actual)

    val correct =
      """
        |{
        |  "id": 123,
        |  "name":"foo-name",
        |  "optField":"foo-opt",
        |  "nonOptId":321
        |}
        |""".stripMargin.trim

    assert(json !== correct)
  }

}

case class Id(value: Int) extends AnyVal

case class Foo(id: Option[Id], name: String, optField: Option[String], nonOptId: Id)

I'm using scala 2.12 and the latest json4s-native version:

    "org.json4s" %% "json4s-native" % "3.6.7"
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Roman
  • 64,384
  • 92
  • 238
  • 332

2 Answers2

3

It looks very similar to this issue, doesn't seem to be fixed or commented on.

A custom serializer would save your day.

object IdSerializer extends CustomSerializer[Id] ( format => (
  { case JInt(a)  => Id(a.toInt) },
  { case a: Id => JInt(a.value) }
))
implicit val formats = DefaultFormats + IdSerializer
val json = writePretty(Foo(Option(Id(123)), "foo-name", Option("foo-opt"), Id(321)))
Feyyaz
  • 3,147
  • 4
  • 35
  • 50
  • I briefly checked the issue tracker before posting, but missed that one. Looks like what you propose is the only way for now. – Roman Sep 02 '19 at 18:02
0

In any case try other options like using of jsoniter-scala instead. It is much safe and efficient than json4s especially in serialization of prettified JSON.

Add/replace dependencies:

libraryDependencies ++= Seq(
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core"   % "0.55.2" % Compile,
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "0.55.2" % Provided // required only in compile-time
)

Derive a codec for the top-level data structure and use it to serialize into a string:

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

case class Id(value: Int) extends AnyVal

case class Foo(id: Option[Id], name: String, optField: Option[String], nonOptId: Id)

implicit val codec: JsonValueCodec[Foo] = JsonCodecMaker.make(CodecMakerConfig())

val json = writeToString(Foo(Option(Id(123)), "foo-name", Option("foo-opt"), Id(321)), WriterConfig(indentionStep = 2))
val correct =
  """{
    |  "id": 123,
    |  "name": "foo-name",
    |  "optField": "foo-opt",
    |  "nonOptId": 321
    |}""".stripMargin
assert(json == correct)

There are also more efficient options to store into byte array, java.nio.ByteBuffer or java.io.OutputStream immediately.

Andriy Plokhotnyuk
  • 7,883
  • 2
  • 44
  • 68