Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circe - Use default fields in case class when decoding/encoding json

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.

like image 260
simen-andresen Avatar asked Nov 20 '17 09:11

simen-andresen


1 Answers

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

like image 55
Dave DeCaprio Avatar answered Oct 18 '22 17:10

Dave DeCaprio