Rich Ashworth
Rich Ashworth

Reputation: 2015

Circe encoder for generic case class with default parameters

I am looking to provide JSON encoders for the following case class:

import io.circe.generic.extras.Configuration

final case class Hello[T](
    source: String, 
    version: Int = 1,
    data: T
)

object Hello {
  implicit val configuration: Configuration = Configuration.default.withDefaults
}

I would ordinarily call deriveEncoder[A] in the companion object, but that doesn't work here as there is no reference or Encoder for T available here.

The Hello type will be provided to clients as a library, so I would like to do as much of the boilerplate as possible within this type rather than depend on client code providing the encoder and decoder. Is there an idiomatic solution to this with circe so that clients provide an encoder/decoder for T and this gets used to derive the encoder/decoder for Hello[T]?

Upvotes: 9

Views: 3721

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149518

Yes, you need to add a context bound requiring an implicit encoder to be present for any type T:

import io.circe.generic.semiauto._

final case class Hello[T](
  source: String,
  version: Int = 1,
  data: T
)

object Hello {
  implicit def helloEncoder[T: Encoder]: Encoder[Hello[T]] = deriveEncoder
}

Such that when the user creates their own Hello[Foo] type, they'll have to make sure that Foo has its own encoder.

Upvotes: 17

Alberto Serna
Alberto Serna

Reputation: 1

This also worked out for me, without using companion object , (case class for "Generic" JsonParserCirce[T] used for SCIO Apache Beam Pipeline)

import com.spotify.scio.coders.Coder
import com.spotify.scio.values.SCollection
import io.circe
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
import io.circe.parser.decode
import org.slf4j.{Logger, LoggerFactory}

import java.util.Locale

case class JsonParserCirce[T](implicit val lazyDecoder: Lazy[DerivedDecoder[T]]) {
  implicit val coderLocale: Coder[Locale] = Coder.kryo[Locale]
  implicit def defaultDecoder: Decoder[T] = deriveDecoder[T]

  def parseJSONStrings(
    messages: SCollection[String]
  )(implicit coder: Coder[T]): (SCollection[T], SCollection[JsonError]) = {
    log.info("Parsing JSON Strings...")
    val jsons: SCollection[Either[circe.Error, T]] = messages.map { s: String => json2CaseClass(s) }

/*My Transformations*/

// String to Json using Circe
  def json2CaseClass(jsonStr: String): Either[circe.Error, T] =
    decode(jsonStr)

[spotify-scio] [apache-beam]

P.S.1: (I only need Decoder[T])

P.S.2: I got some inspiration here too https://github.com/circe/circe/issues/1442

P.S.3: (implicit val lazyDecoder: Lazy[DerivedDecoder[T]]) is needed othwerwise the compiler throws could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[T] implicit def defaultDecoder: Decoder[T] = deriveDecoder[T]

Upvotes: 0

Related Questions