As indicated in the documentation, the Reads
/OWrites
/OFormat
instances for sealed family (sealed trait and subtypes) can be generated using Json.{read,write,format}
macros.
import play.api.libs.json._
sealed trait DataFormat
case object JsonFormat extends DataFormat
case object InvalidFormat extends DataFormat
object DataFormat {
implicit val format: OFormat[DataFormat] = {
implicit def jsf = Json.format[JsonFormat.type]
implicit def ivf = Json.format[InvalidFormat.type]
Json.format[DataFormat]
}
}
Please use the Scala naming, and do not name types (classes, objects, traits in UPPERCASE).
Then:
scala> Json.toJson[DataFormat](JsonFormat)
res1: play.api.libs.json.JsValue = {"_type":"JsonFormat"}
scala> Json.toJson(InvalidFormat: DataFormat)
res2: play.api.libs.json.JsValue = {"_type":"InvalidFormat"}
You can have a look at this previous question for the limitations.
You could also have a look at Enumeratum support for Play-JSON (to use enumerated types with Play-JSON).
About alternative code in comment:
object DataFormat {
// Note: dataFormatObject is not an object name according Scala naming
implicit object DataFormatObject extends Format[DataFormat] {
implicit def reads(json: JsValue) = json match {
case JsString("JSON") =>
JsSuccess(JSON)
case _ =>
JsError(INVALID.toString)
}
implicit def writes(dataFormat: DataFormat) =
JsString(dataFormat.toString)
}
}
// No need to define it again as available in companion object
implicit lazy val dataFormatWrites: Format[DataFormat] =
DataFormat.dataFormatObject
So there is wrong naming and val dataFormatWrites
is not need.
If you want to represent enumerated type in Play JSON, I strongly advice to have a look at Enumeratum-Play-JSON as mentioned previously.
Anyway it can be done by composing already provided Reads[String]
and Writes[String]
instances.
import play.api.libs.json._
sealed trait DataFormat
case object JsonFormat extends DataFormat
case object InvalidFormat extends DataFormat
object DataFormat {
implicit def writes: Writes[DataFormat] =
implicitly[Writes[String]]. // Resolve default `Writes[String]`
contramap[DataFormat] {
case JsonFormat => "JSON"
case _ => "INVALID"
}
implicit def reads: Reads[DataFormat] =
implicitly[Reads[String]]. // Resolve default `Reads[String]`
collect[DataFormat](JsonValidationError("Unsupported DataFormat")) {
case "JSON" => JsonFormat
case _ => InvalidFormat
}
}
scala> Json.toJson[DataFormat](JsonFormat)
res1: play.api.libs.json.JsValue = "JSON"
scala> Json.toJson(InvalidFormat: DataFormat)
res3: play.api.libs.json.JsValue = "INVALID"
scala> JsString("JSON").validate[DataFormat]
res4: play.api.libs.json.JsResult[DataFormat] = JsSuccess(JsonFormat,)
scala> JsString("INVALID").validate[DataFormat]
res5: play.api.libs.json.JsResult[DataFormat] = JsSuccess(InvalidFormat,)
Moreover, I would not define case object InvalidFormat
, as it doesn't represent a format, but rather an error when trying to work with supported format.
That's where validation types are useful, there with Play-JSON it's JsResult
, so InvalidFormat
should rather be a JsError[DataFormat]
.
import play.api.libs.json._
sealed trait DataFormat
case object JsonFormat extends DataFormat
case object AnotherFormat extends DataFormat // at least 2 subtypes, otherwise no need to have a DataFormat trait but rather directly use JsonFormat
object DataFormat {
implicit def writes: Writes[DataFormat] =
implicitly[Writes[String]]. // Resolve default `Writes[String]`
contramap[DataFormat] {
case JsonFormat => "JSON"
case AnotherFormat => "ANOTHER"
}
implicit def reads: Reads[DataFormat] =
implicitly[Reads[String]]. // Resolve default `Reads[String]`
collect[DataFormat](JsonValidationError("Invalid DataFormat")) {
case "JSON" => JsonFormat
case "ANOTHER" => AnotherFormat
}
}
So then:
scala> Json.toJson[DataFormat](JsonFormat)
res0: play.api.libs.json.JsValue = "JSON"
scala> Json.toJson(AnotherFormat: DataFormat)
res2: play.api.libs.json.JsValue = "ANOTHER"
scala> JsString("JSON").validate[DataFormat]
res3: play.api.libs.json.JsResult[DataFormat] = JsSuccess(JsonFormat,)
scala> JsString("ANOTHER").validate[DataFormat]
res4: play.api.libs.json.JsResult[DataFormat] = JsSuccess(AnotherFormat,)
scala> JsString("FOO").validate[DataFormat]
res5: play.api.libs.json.JsResult[DataFormat] = JsError(List((,List(JsonValidationError(List(Invalid DataFormat),WrappedArray())))))