Skip to content

Instantly share code, notes, and snippets.

@oscarduignan
Created September 22, 2025 12:45
Show Gist options
  • Select an option

  • Save oscarduignan/1bd9ebdd0654a2f7e9664a8913954c63 to your computer and use it in GitHub Desktop.

Select an option

Save oscarduignan/1bd9ebdd0654a2f7e9664a8913954c63 to your computer and use it in GitHub Desktop.
Example of the stuff I was saying about "explicitly"? mapping rather than using functional combinators
//> using scala 2.13
//> using dep org.playframework::play-json:3.0.5
//> using dep com.lihaoyi::pprint:0.9.3
import play.api.libs.json._
sealed trait Content
case object Empty extends Content
case class Text(value: String) extends Content
case class HtmlContent(value: String) extends Content
object Content {
implicit val formatContent: Format[Content] = new Format[Content] {
def reads(json: JsValue): JsResult[Content] = json match {
case JsObject(values) =>
values.get("html").map({
case JsString(s) => JsSuccess(HtmlContent(s))
case _ => JsError(JsPath \ "html", "Expected html to be string")
}).orElse(values.get("text").map({
case JsString(s) => JsSuccess(Text(s))
case _ => JsError(JsPath \ "text", "Expected text to be string")
})).getOrElse(
JsError("Expected object with html or text key")
)
case _ =>
JsError("Expected object with html or text key")
}
def writes(content: Content): JsValue = content match {
case Empty => JsNull
case Text(value) => JsObject(Map("text" -> JsString(value)))
case HtmlContent(value) => JsObject(Map("html" -> JsString(value)))
}
}
}
case class ServiceNavigationSlot(
end: Content = Empty,
start: Content = Empty,
navigationEnd: Content = Empty,
navigationStart: Content = Empty
)
object ServiceNavigationSlot {
implicit private val formatAlwaysHtmlContent: Format[Content] = new Format[Content] {
def reads(json: JsValue): JsResult[Content] = json match {
case JsNull => JsSuccess(Empty)
case JsString(s) => JsSuccess(HtmlContent(s))
case _ => JsError("Expected string or null")
}
def writes(content: Content): JsValue = content match {
case Empty => JsNull
case Text(value) => JsString(value)
case HtmlContent(value) => JsString(value)
}
}
// Json.using[Json.WithDefaultValues] isn't needed in scala 3 - not sure exactly why it is in scala 2.13
implicit val formatServiceNavigationSlot: Format[ServiceNavigationSlot] =
Json.using[Json.WithDefaultValues].format[ServiceNavigationSlot]
}
object Example extends App {
pprint.pprintln(Json.parse(
"""
|{
| "end": "test",
| "start": "test",
| "navigationEnd": "test"
|}""".stripMargin).as[ServiceNavigationSlot])
pprint.pprintln(Json.parse(
"""
|{ "text": "test" }
|""".stripMargin).as[Content])
pprint.pprintln(Json.parse(
"""
|{ "html": "<p>test</p>" }
|""".stripMargin).validate[Content])
pprint.pprintln(Json.parse(
"""
|{ "html": "<p>test</p>", "text": "test" }
|""".stripMargin).validate[Content])
pprint.pprintln(Json.parse(
"""
|{ "html": 123 }
|""".stripMargin).validate[Content])
pprint.pprintln(Json.stringify(Json.toJson(ServiceNavigationSlot(
start = HtmlContent("<p>test</p>"),
end = Text("test")
))))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment