11
votes

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.

1
Would using a Option[Boolean] be a valid workaround? And setting it to None when not needed? - Yuval Itzchakov
@YuvalItzchakov it's a good suggestion, but in this case we use the case class also with quill. Since the default field should be NOT NULL in our database, it would be nice to keep it as a non-option in the case class. - simen-andresen
@simen-andresen You should consider accepting the below solution - Lukasz

1 Answers

18
votes

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