Last active
October 27, 2021 06:23
-
-
Save filosganga/0ea61a4b7859a42c77feb4fa6e08c6bf to your computer and use it in GitHub Desktop.
The JSON tagger allows to decode an ADT encoded in JSON avoiding the focus to switch to a different branch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.circe._ | |
import io.circe.generic.semiauto._ | |
import cats.syntax.all._ | |
object JsonTagger { | |
private implicit class Tagger[A](d: Decoder[A]) { | |
def tag(accessor: String): Decoder[Decoder[A]] = | |
Decoder | |
.instance { inputJson => | |
inputJson.downField(accessor) match { | |
case err: FailedCursor => Left(DecodingFailure(s"key $accessor expected", err.history)) | |
case innerJson: HCursor if innerJson.value.isNull => | |
Left(DecodingFailure(s"key $accessor expected to be not null", innerJson.history)) | |
case innerJson: HCursor => Right(innerJson) | |
} | |
} | |
.map(outJson => Decoder.instance(_ => d(outJson))) | |
} | |
sealed trait Animal | |
object Animal { | |
case class Dog(name: String, tailLength: Int) extends Animal | |
case class Cat(name: String, preferredMilk: String) extends Animal | |
} | |
/** | |
* This decoder does not return an useful message, because it will always return the failure from the `decodeDog` even if the JSON is for a cat. | |
* | |
* {{{ | |
* { | |
* "animal": { | |
* "cat": { | |
* "name": "Toby" | |
* } | |
* } | |
* } | |
* }}} | |
* | |
* This JSON snippet will fail for something like `dog` is an ivalid cursor. | |
*/ | |
val buggedDecoderForAnimal: Decoder[Animal] = { | |
val decodeCat = deriveDecoder[Cat].prepare(hc => hc.downField("cat")) | |
val decodeDog = deriveDecoder[Dog].prepare(hc => hc.downField("dog")) | |
decodeCat.orElse(decodeDog) | |
} | |
/** | |
* This decoder returns an useful message, because it will not fallback to the other decoder if "cat" or "dog" keys exist. | |
* | |
* {{{ | |
* { | |
* "animal": { | |
* "cat": { | |
* "name": "Toby" | |
* } | |
* } | |
* } | |
* }}} | |
* | |
* This JSON snippet will fail for something like `preferredMilk` is null or an ivalid cursor. So it does not fallback to "dog" | |
. */ | |
val taggedDecoderForAnimal: Decoder[Animal] = { | |
val decodeCat = deriveDecoder[Cat].tag("cat") | |
val decodeDog = deriveDecoder[Dog].tag("dog") | |
decodeCat.orElse(decodeDog) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment