Search code examples
scalaplay-json

Serialize objects as JSON primitive type in Scala (Play Framework)


check out the following example:

object MyTrait {
  def apply(value: Int): MyTrait = value match {
    case 0 => A
    case 1 => B
    case 2 => C
    case _ => C
  }

  def unapply(arg: MyTrait): Int = arg.value
}

case object A extends MyTrait {
  val value = 0
}

case object B extends MyTrait {
  val value = 1
}

case object C extends MyTrait {
  val value = 2
}

case class Test(value: MyTrait)

implicit val testFormatter = Json.format[Test]

all right, this code won't work because i didn't create a JSON serializer for my type "MyTrait". I could do it the way the Play Framework show us to do in the documentation, but it would make a json looking like

{ value: { whatever: 1 } }

and i would like it to look like

{ value: 1 }

In short, i would like my MyTrait objetcs to be interpreted as primitive types (Int) instead of a nested Json Oject.

If someone could help me with that i would be greatful. Thank you by advance!


Solution

  • The documentation indicates how to provide custom Writes, there to MyTrait to serialize only it's inner value.

    sealed trait MyTrait {
      def value: Int
    }
    
    case object A extends MyTrait {
      val value = 0
    }
    
    case object B extends MyTrait {
      val value = 1
    }
    
    case object C extends MyTrait {
      val value = 2
    }
    
    object MyTrait {
      import play.api.libs.json._
    
      def apply(value: Int): MyTrait = value match {
        case 0 => A
        case 1 => B
        case 2 => C
        case _ => C
      }
    
      def unapply(arg: MyTrait): Int = arg.value
    
      // ---v
      implicit val writes: Writes[MyTrait] =
        Writes[MyTrait] { v => JsNumber(v.value) }
    }
    

    Then when serializing MyTrait instances (note the type ascription bellow is required as Writes is invariant):

    Json.toJson(A: MyTrait)
    // --> res2: play.api.libs.json.JsValue = 0
    

    And so about Test class:

    case class Test(value: MyTrait)
    
    object Test {
      import play.api.libs.json._
    
      implicit val writes: OWrites[Test] = Json.writes[Test]
    }
    
    Json.toJson(Test(A))
    // ---> res1: play.api.libs.json.JsValue = {"value":0}
    

    I would rather recommend to have a look at Enumeratum for the enumerated type MyTrait, or to refactor MyTrait as a Value class and use Json.valueFormat as below.

    import play.api.libs.json._
    
    final class MyTrait private(val value: Int) extends AnyVal
    
    object MyTrait {
      val A = new MyTrait(0)
      val B = new MyTrait(1)
      val C = new MyTrait(2)
    
      implicit val format: Format[MyTrait] = Json.valueFormat[MyTrait]
    }
    
    case class Test(value: MyTrait)
    
    object Test {
      implicit val format: OFormat[Test] = Json.format
    }
    
    scala> Json.toJson(Test(MyTrait.A))
    res0: play.api.libs.json.JsValue = {"value":0}