Lets say I have this case class:
case class Foo(bar: String, baz: Boolean = false)
which is used in when decoding/encoding API requests/responses using akka-http-json
in an example similar to this:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives
import akka.stream.{ ActorMaterializer, Materializer }
import scala.io.StdIn
object ExampleApp {
private final case class Foo(bar: String, baz: Boolean = false)
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
Http().bindAndHandle(route, "127.0.0.1", 8000)
StdIn.readLine("Hit ENTER to exit")
system.terminate()
}
private def route(implicit mat: Materializer) = {
import Directives._
import FailFastCirceSupport._
import io.circe.generic.auto._
pathSingleSlash {
post {
entity(as[Foo]) { foo =>
complete {
foo
}
}
}
}
}
}
This works fine as long as the json message includes the baz field. However, I want to be able to send a json message {bar: "something"} and let the result use Foo's default value for baz. Is there any configuration in circe or akka-http-json that could make this work?
Also, would be nice to ignore the baz field when encoding to json again, but this is not that important.
Edit:
I know I can do something like this:
implicit val fooEncoder: Encoder[Foo] = new Encoder[Foo] {
final def apply(a: Foo): Json = Json.obj(
("id", Json.fromString(a.bar))
)
}
implicit val fooDecoder: Decoder[Foo] = new Decoder[Decoder] {
final def apply(c: HCursor): Decoder.Result[Decoder] =
for {
bar <- c.downField("bar").as[String]
} yield {
Foo(bar)
}
}
but was hoping for an easier-to-maintain solution, solving the general case of not requiring the default fields in the json message.
You can do this using the circe-generic-extras package. It's a separate dependency you have to put in your build. For sbt, that's:
libraryDependencies += "io.circe" %% "circe-generic-extras" % "0.8.0"
Then, in your route function, replace
import io.circe.generic.extras.Configuration
import io.circe.generic.auto._
with:
import io.circe.generic.extras.auto._
implicit val customConfig: Configuration = Configuration.default.withDefaults
The encoders this generates will always include the default fields.
For more info see the circe release notes at: https://github.com/circe/circe/releases/tag/v0.6.0-RC1
Related
I have a wrapper trait that extends BasicFormats from spray json https://github.com/spray/spray-json/blob/release/1.3.x/src/main/scala/spray/json/BasicFormats.scala
but I would like to override the behavior of implicit object BigDecimalJsonFormat extends JsonFormat[BigDecimal] to add rounding logic. Example being
import spray.json._
import scala.math.BigDecimal.RoundingMode
case class Foo(i: BigDecimal)
object Foo extends DefaultJsonProtocol {
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] =
new JsonFormat[BigDecimal] {
def write(f: BigDecimal) = JsNumber(f.setScale(2, RoundingMode.HALF_UP))
def read(json: JsValue): BigDecimal =
DefaultJsonProtocol.BigDecimalJsonFormat.read(json)
}
implicit val fooFormatter = jsonFormat1(this.apply)
}
scastie snippet:
https://scastie.scala-lang.org/9RNhajzGRDGMX5QsuAohVA
Great question, I wasn't even aware this may cause so many problems. The solution I am about to propose is probably not the cleanest, but it does the job... First of all, you don't have to extend DefaultJsonProtocol, you might also import it's members:
object Foo {
import DefaultJsonProtocol._
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] = ...
implicit val fooFormatter = jsonFormat1(this.apply)
}
This doesn't solve the problem but that way you can exclude some of the members so that they are not imported. Here's the syntax for that
import DefaultJsonProtocol.{BigDecimalJsonFormat => _, _}
It basically says: don't import BigDecimalJsonFormat but import the rest.
And the following code sums it up.
import spray.json._
import scala.math.BigDecimal.RoundingMode
case class Foo(i: BigDecimal)
object Foo {
import DefaultJsonProtocol.{BigDecimalJsonFormat => _, _}
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] =
new JsonFormat[BigDecimal] {
def write(f: BigDecimal) = JsNumber(f.setScale(2, RoundingMode.HALF_UP))
def read(json: JsValue): BigDecimal =
DefaultJsonProtocol.BigDecimalJsonFormat.read(json)
}
implicit val fooFormatter = jsonFormat1(this.apply)
}
Am currently learning the basics of RESTFUL API, with Play and am getting issues: am following some longtime tutorial and think am failing with right scala syntax! need help, thanks
here is the screenshot of error
package controllers
import play.api.libs.json.Json
import javax.inject.Inject
import play.api.Configuration
import play.api.mvc.{AbstractController, ControllerComponents}
import scala.concurrent.ExecutionContext
class PlacesController #Inject()(cc: ControllerComponents)(implicit assetsFinder: AssetsFinder, ec: ExecutionContext, configuration: Configuration)
extends AbstractController(cc) {
case class PlacesController(id: Int, name: String)
val thePlaces: List = List(
thePlaces(1, "newyork"),
thePlaces(2, "chicago"),
thePlaces(3, "capetown")
)
implicit val thePlacesWrites = Json.writes[PlacesController]
def listPlaces = Action {
val json = Json.toJson(thePlaces)
Ok(json)
}}
There are quite a few problems with your code. You are defining thePlaces while calling thePlaces itself on the right-hand-side of the definition.
Also, your naming is confusing.
Try this:
final case class Place(id: Int, name: String)
object Place {
implicit val placeWrites = Json.writes[Place]
}
class PlacesController ... {
val thePlaces: List[Place] = List(
Place(1, "newyork"),
Place(2, "chicago"),
Place(3, "capetown")
)
def listPlaces = Action {
val json = Json.toJson(thePlaces)
Ok(json)
}
}
Finally got the answer, hope this can be of help to some other person in future!!
class PlacesController #Inject()(cc: ControllerComponents)(implicit assetsFinder: AssetsFinder, ec: ExecutionContext, configuration: Configuration)
extends AbstractController(cc) {
case class PlacesController(id: Int, name: String)
val thePlaces: List[(Int, String)] = List(
(1, "newyork"),
(2, "chicago"),
(3, "capetown")
)
implicit val thePlacesWrites = Json.writes[PlacesController]
def listPlaces = Action {
val json = Json.toJson(thePlaces)
Ok(json)
}
}
In Scala with Play and Slick, I've implemented enum Country as a case class, and would like to read/write it to JSON as a string. This is the complete code:
package models.enums
import play.api.libs.json._
import slick.lifted.MappedTo
case class Country(id: String) extends MappedTo[String] {
override def value: String = id
}
object Country {
val France = Country("FR")
val Germany = Country("DE")
implicit val writes = Writes[Country](
(site: Country) => JsString(site.value)
)
// this line I need help with
implicit val reads = Json.reads[Country]
}
The Country enum is used by other models, e.g.
{
"name": "Robo"
"country": "DE"
}
The code above writes the enum as a plain String which is what I want, but I can't figure out how to implement the implicit read part. Currently the read would only work if the JSON is like:
{
"name": "Robo"
"country": {
"id": "DE"
}
}
I'm sure the answer is simple, just can't figure out the correct syntax.
import play.api.libs.json._
import play.api.libs.json.Reads._
implicit val reads = new Reads[Country] {
override def reads(json: JsValue): JsResult[Country] = for {
id <- (json \ "country").validate[String]
} yield Country(id)
}
(json \ "country").validate[String] returns a JsResult[String] which is then mapped (via yield) to a JsResult[Country]
or
import play.api.libs.json._
import play.api.libs.json.Reads._
implicit val reads = new Reads[Country] {
override def reads(json: JsValue): JsResult[Country] =
(json \ "country").validate[String].map(Country(_))
or
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
implicit val reads: Reads[Country] = (__ \ "country").reads[String].map(Country(_))
EDIT:
Regarding your comment on validation, I'd just add that to your case class, not the json parser:
case class Country(id: String) extends MappedTo[String] {
private val validIds = Set(France, Germany).map(_.id)
require(validIds contains id, s"id must be one of $validIds")
override def value: String = id
}
the trick is that statements you put in the body of your class will be part of the constructor. The require call there will throw an exception if anyone tries to create a Country with an ID which is not in your list of allowed IDs.
I recommend using sealed class. If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
Example from my project:
import com.pellucid.sealerate
import play.api.libs.json._
sealed abstract class TokenKind(val value: String)
object TokenKind {
case object PasswordReset extends TokenKind("passwordReset")
case object AccountConfirmation extends TokenKind("accountConfirmation")
def values: Set[TokenKind] = sealerate.values[TokenKind]
def apply(value: String): TokenKind = values.find(_.value == value).getOrElse(throw new RuntimeException(s"Can't construct TokenKind from: $value"))
implicit def tokenKindWrites = new Writes[TokenKind] {
override def writes(o: TokenKind): JsValue = JsString(o.value)
}
implicit def tokenKindReads = new Reads[TokenKind] {
override def reads(json: JsValue): JsResult[TokenKind] = json match {
case JsString(string) => JsSuccess(TokenKind(string))
case _ => JsError("validate.error.invalidTokenKind")
}
}
}
I had a similar problem that solved this way:
object VoteType extends Enumeration {
val Up, Down = Value
implicit val voteTypeMappeer = MappedColumnType.base[VoteType.Value, String](_.toString, VoteType.withName)
}
I am writing a RESTful interface and I would like to marshall and unmarshall JSON ready for Ember Data.
The wrinkle is that Ember Data wants the entity name and the two libraries I've tried, spray-json and json4s, don't appear to do this easily.
Desired Ember Data format
{
"coursePhoto": {
"photoId": 1
}
}
Current default format:
{"photoId":15}
This should come from a case class:
case class CoursePhoto(photoId: Long)
I did get it running with the following custom code:
object PtolemyJsonProtocol extends DefaultJsonProtocol {
implicit object CoursePhotoFormat extends RootJsonFormat[CoursePhoto] {
def write(cp: CoursePhoto) =
JsObject("CoursePhoto" -> JsObject("photoId" -> JsNumber(cp.photoId)))
def read(value: JsValue) = value match {
case coursePhotoJsObject: JsObject => {
CoursePhoto(coursePhotoJsObject.getFields("CoursePhoto")(0).asJsObject
.getFields("photos")(0).asInstanceOf[JsArray].elements(0)
.asInstanceOf[JsNumber].value.toLong)
}
case _ => deserializationError("CoursePhoto expected")
}
}
That code seems horrifyingly fragile and ugly with all the asInstanceOf and (0).
Given that I'm writing in Spray with Scala what's the nice way to get named root JSON output? I am quite happy to do this with any JSON library that integrates nicely with Spray and is reasonably performant.
Is the following solving your problem?
scala> import spray.json._
import spray.json._
scala> import DefaultJsonProtocol._
import DefaultJsonProtocol._
scala> case class CoursePhoto(photoId: Long)
defined class CoursePhoto
scala> case class CoursePhotoEmber(coursePhoto: CoursePhoto)
defined class CoursePhotoEmber
scala> implicit val jsonFormatCoursePhoto = jsonFormat1(CoursePhoto)
jsonFormatCoursePhoto: spray.json.RootJsonFormat[CoursePhoto] = spray.json.ProductFormatsInstances$$anon$1#6f5d66b6
scala> implicit val jsonFormatCoursePhotoEmber = jsonFormat1(CoursePhotoEmber)
jsonFormatCoursePhotoEmber: spray.json.RootJsonFormat[CoursePhotoEmber] = spray.json.ProductFormatsInstances$$anon$1#401a0d22
scala> """{ "coursePhoto": { "photoId": 1 } }""".parseJson.convertTo[CoursePhotoEmber]
res0: CoursePhotoEmber = CoursePhotoEmber(CoursePhoto(1))
scala> res0.toJson
res1: spray.json.JsValue = {"coursePhoto":{"photoId":1}}
This problem made me wonder if it were possible to do it in a re-usable way. I believe I've figured out a reasonable way to do this for multiple types.
object PtolemyJsonProtocol extends DefaultJsonProtocol {
implicit val CoursePhotoFormat = new NamedRootFormat("CoursePhoto", jsonFormat1(CoursePhoto))
}
import PtolemyJsonProtocol._
class NamedRootFormat[T](rootName: String, delegate: RootJsonFormat[T]) extends RootJsonFormat[T] {
def write(obj: T): JsValue = {
JsObject((rootName, delegate.write(obj)))
}
def read(json: JsValue): T = json match {
case parentObject: JsObject => {
delegate.read(parentObject.getFields(rootName).head)
}
case _ => deserializationError("CoursePhoto expected")
}
}
I am trying to use my domain objects as a request/response body parameters. I am using spray-routing and as[T] to unmarshall object. But constantly I'm getting could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller. Eventhough I've added implicit unmarshaller manually to companion object I'm getting same error. I have no idea what is wrong. This JSON serializer/deserializer for Event I've written because I need to serialize joda DateTime object.
package services
import spray.routing.HttpService
import org.joda.time.DateTime
import org.joda.time.format.{DateTimeFormatter, ISODateTimeFormat}
import spray.httpx.unmarshalling.FromRequestUnmarshaller
import spray.json._
import services.EventsService.Event
import spray.httpx.SprayJsonSupport
trait EventsService extends HttpService {
val eventsRoute =
path("/events") {
get {
import EventsService._
entity(as[Event]) { event =>
complete(s"${event.toString}")
}
}
}
}
object EventsService extends DefaultJsonProtocol with SprayJsonSupport{
trait DateFormatter {
val formatter: DateTimeFormatter
}
trait DateParser {
val parser: DateTimeFormatter
}
implicit object EventFormatter extends RootJsonFormat[Event] with DateFormatter with DateParser {
def read(json: JsValue): Event = json match {
case obj: JsObject =>
val name = obj.fields.get("name").map(_.asInstanceOf[JsString].value).
getOrElse(deserializationError("field name not present"))
val city = obj.fields.get("city").map(_.asInstanceOf[JsString].value).
getOrElse(deserializationError("field city not present"))
val starts = obj.fields.get("starts").map(x => parser.parseDateTime(x.asInstanceOf[JsString].value)).
getOrElse(deserializationError("field starts not present"))
val ends = obj.fields.get("ends").map(x => parser.parseDateTime(x.asInstanceOf[JsString].value)).
getOrElse(deserializationError("ends field not present"))
Event(name, city, starts, ends)
case _ => deserializationError("wrong object to deserialize")
}
def write(e: Event): JsValue =
JsObject(Map(
"name" -> JsString(e.name),
"city" -> JsString(e.city),
"starts" -> JsString(formatter.print(e.starts)),
"ends" -> JsString(formatter.print(e.ends))
))
val formatter = ISODateTimeFormat.dateTimeNoMillis()
val parser = ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed()
}
case class Event(name: String, city: String, starts: DateTime, ends: DateTime)
}
Ok, I've figured that out. The order of structures is wrong. First should be companion object and second trait with route. I don't know why, yet but it works now.