circe-tagged-adt-codec-models


License

License

GroupId

GroupId

org.latestbit
ArtifactId

ArtifactId

circe-tagged-adt-codec-models_sjs0.6_2.13
Last Version

Last Version

0.9.1
Release Date

Release Date

Type

Type

jar
Description

Description

circe-tagged-adt-codec-models
circe-tagged-adt-codec-models
Project URL

Project URL

https://github.com/abdolence/circe-tagged-adt-codec
Project Organization

Project Organization

org.latestbit
Source Code Management

Source Code Management

https://github.com/abdolence/circe-tagged-adt-codec

Download circe-tagged-adt-codec-models_sjs0.6_2.13

How to add to project

<!-- https://jarcasting.com/artifacts/org.latestbit/circe-tagged-adt-codec-models_sjs0.6_2.13/ -->
<dependency>
    <groupId>org.latestbit</groupId>
    <artifactId>circe-tagged-adt-codec-models_sjs0.6_2.13</artifactId>
    <version>0.9.1</version>
</dependency>
// https://jarcasting.com/artifacts/org.latestbit/circe-tagged-adt-codec-models_sjs0.6_2.13/
implementation 'org.latestbit:circe-tagged-adt-codec-models_sjs0.6_2.13:0.9.1'
// https://jarcasting.com/artifacts/org.latestbit/circe-tagged-adt-codec-models_sjs0.6_2.13/
implementation ("org.latestbit:circe-tagged-adt-codec-models_sjs0.6_2.13:0.9.1")
'org.latestbit:circe-tagged-adt-codec-models_sjs0.6_2.13:jar:0.9.1'
<dependency org="org.latestbit" name="circe-tagged-adt-codec-models_sjs0.6_2.13" rev="0.9.1">
  <artifact name="circe-tagged-adt-codec-models_sjs0.6_2.13" type="jar" />
</dependency>
@Grapes(
@Grab(group='org.latestbit', module='circe-tagged-adt-codec-models_sjs0.6_2.13', version='0.9.1')
)
libraryDependencies += "org.latestbit" % "circe-tagged-adt-codec-models_sjs0.6_2.13" % "0.9.1"
[org.latestbit/circe-tagged-adt-codec-models_sjs0.6_2.13 "0.9.1"]

Dependencies

compile (5)

Group / Artifact Type Version
org.scala-lang : scala-library jar 2.13.2
org.scala-js : scalajs-library_2.13 jar 0.6.32
io.circe : circe-core_sjs0.6_2.13 jar 0.13.0
io.circe : circe-generic_sjs0.6_2.13 jar 0.13.0
io.circe : circe-parser_sjs0.6_2.13 jar 0.13.0

test (3)

Group / Artifact Type Version
org.scala-js : scalajs-test-bridge_2.13 jar 0.6.32
org.scalactic : scalactic_sjs0.6_2.13 jar 3.1.1
org.scalatest : scalatest_sjs0.6_2.13 jar 3.1.1

Project Modules

There are no modules declared in this project.

Circe encoder/decoder implementation for ADT to JSON with a configurable type field.

Maven Central

This library provides an efficient, type safe and macro based ADT to JSON encoder/decoder for circe with configurable JSON type field mappings.

When you have ADTs (as trait and case classes) defined like this

sealed trait TestEvent

case class MyEvent1(anyYourField : String /*, ...*/) extends TestEvent
case class MyEvent2(anyOtherField : Long /*, ...*/) extends TestEvent
// ...

and you would like to encode them to JSON like this:

{
  "type" : "my-event-1",
  "anyYourField" : "my-data", 
  "..." : "..."
}

The main objectives here are:

  • Avoid JSON type field in Scala case class definitions.
  • Configurable JSON type field values and their mapping to case classes. They don't have to be Scala class names.
  • Avoid writing circe Encoder/Decoder manually.
  • Check at the compile time JSON type field mappings and Scala case classes.

Scala support

  • Scala v2.12 / v2.13
  • Scala.js v0.6 / v1

Getting Started

Add the following to your build.sbt:

libraryDependencies += "org.latestbit" %% "circe-tagged-adt-codec" % "0.9.1"

or if you need Scala.js support:

libraryDependencies += "org.latestbit" %%% "circe-tagged-adt-codec" % "0.9.1"

Usage

import io.circe._
import io.circe.parser._
import io.circe.syntax._

// This example uses auto coding for case classes. 
// You decide here if you need auto/semi/custom coders for your case classes.
import io.circe.generic.auto._ 

// One import for this ADT/JSON codec
import org.latestbit.circe.adt.codec._


sealed trait TestEvent

//@JsonAdt annotation is required only if you'd like to specify JSON type field value yourself. 
// Otherwise it would be the class name  

@JsonAdt("my-event-1") 
case class MyEvent1(anyYourField : String /*, ...*/) extends TestEvent
@JsonAdt("my-event-2")
case class MyEvent2(anyOtherField : Long /*, ...*/) extends TestEvent

// Encoding

implicit val encoder : Encoder[TestEvent] = JsonTaggedAdtCodec.createEncoder[TestEvent]("type")

val testEvent : TestEvent = TestEvent1("test")
val testJsonString : String = testEvent.asJson.dropNullValues.noSpaces

// Decoding
implicit val decoder : Decoder[TestEvent] = JsonTaggedAdtCodec.createDecoder[TestEvent]("type")

decode[TestEvent] (testJsonString) match {
   case Right(model : TestEvent) => // ...
}

Configure and customise base ADT Encoder/Decoder implementation

In case you need a slightly different style of coding of your ADT to JSON, there is an API to change it.

Let's assume that you'd like to produce a bit different JSON like this:

{
  "type" : "my-event-1",
  "body" : {
    "anyYourField" : "my-data", 
     "..." : "..."
  }  
}

Then you should specify it with your own implementation:

implicit val encoder: Encoder[TestEvent] =
    JsonTaggedAdtCodec.
        createEncoderDefinition[TestEvent] { case (converter, obj) =>

            // converting our case classes accordingly to obj instance type
            // and receiving JSON type field value from annotation
            val (jsonObj, typeFieldValue) = converter.toJsonObject(obj)

            // Our custom JSON structure
            JsonObject(
                "type" -> Json.fromString(typeFieldValue),
                "body" -> Json.fromJsonObject(jsonObj)
            )
        }

implicit val decoder: Decoder[TestEvent] =
    JsonTaggedAdtCodec.
        createDecoderDefinition[TestEvent] { case (converter, cursor) =>
            
            // Reading JSON type field value
            cursor.get[Option[String]]("type").flatMap {
                case Some(typeFieldValue) =>
                    // Decode a case class from body accordingly to typeFieldValue
                    converter.fromJsonObject(
                        jsonTypeFieldValue = typeFieldValue,
                        cursor = cursor.downField("body")
                    )
                case _ =>
                    Decoder.failedWithMessage(s"'type' isn't specified in json.")(cursor)
            }
        }

Complex ADT definitions and trait inheritance

All the following examples are support for this codec:

sealed trait MyTrait
case class MyCaseClass() extends MyTrait
// case objects
case object MyCaseObject extends MyTrait 

// trait inheritance with passing through tags - 
// so, direct children of MyTrait and 
// direct children of MyChildTrait now
// share the same tags namespace 
@JsonAdtPassThrough
sealed trait MyChildTrait extends MyTrait 
case class MyChildCaseClass() extends MyChildTrait
case class MyOtherChildCaseClass() extends MyChildTrait

// Now this is has its own decoder/encoder 
// and the children of MyIsolatedChildTrait have their own tags
sealed trait MyIsolatedChildTrait extends MyTrait 
case class MyIsolatedCaseClass() extends MyIsolatedChildTrait
case class MyIsolatedOtherChildCaseClass() extends MyIsolatedChildTrait

// The same like previous, except here we now define our own tag on a child trait 
// (instead of default behaviour where a tag would be a trait name) 
@JsonAdt("isolated-trait-2")
sealed trait MySecondIsolatedChildTrait extends MyTrait 

Pure enum constants / case objects ADT definitions support

Sometimes you just need tags themselves for declarations like this without any additional type tags:

sealed trait MyEnum
@JsonAdt("tag-1")
case object Enum1 extends MyEnum

@JsonAdt("tag-2")
case object Enum2 extends MyEnum
//...
 

to produce JSON strings for enum constants in json (instead of objects). To help with this scenario, ADT codec provides the specialized encoder and decoder implementations:

implicit val encoder : Encoder[MyEnum] = JsonTaggedAdtCodec.createPureEnumEncoder[MyEnum]()
implicit val decoder : Decoder[MyEnum] = JsonTaggedAdtCodec.createPureEnumDecoder[MyEnum]()

Licence

Apache Software License (ASL)

Author

Abdulla Abdurakhmanov

Versions

Version
0.9.1
0.9.0
0.8.0
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.1
0.4.0
0.3.0