mbari-org / annosaurus

Service for storing and retrieving video/image annotations from VARS
https://docs.mbari.org/annosaurus/
Apache License 2.0
1 stars 1 forks source link

Remove all GSON serialization and switch to circe #22

Closed hohonuuli closed 9 months ago

hohonuuli commented 10 months ago

Need to deal with snake_case/camelCase divide. Tip from Stackoverflow:


Circe gets field names from your case class instance and traverses through JSON using cursor, tries to get the value of each field name and tries to convert it to your desirable type.

It means that your decoder won't be able to process both cases.

The solution to this problem is to write two decoders:

Basic decoder (deriveEncoder will work) Encoder which uses HCursor to navigate through your JSON and get snake case keys

val decoderDerived: Decoder[Camel] = deriveDecoder
val decoderCamelSnake: Decoder[Camel] = (c: HCursor) =>
    for {
      firstName <- c.downField("first_name").as[String]
      lastName <- c.downField("last_name").as[String]
      waterPerDay <- c.downField("water_per_day").as[Int]
    } yield {
      Camel(firstName, lastName, waterPerDay)
    }

Then you can combine these two decoders into one using Decoder#or

implicit val decoder: Decode[Camel] = decoderDerived or decoderCamelSnake

Decoder#or will try to decode using first decoder, and if it fails, then it will try out the second one.

hohonuuli commented 10 months ago

As part of this, I'm hoping to support both the original, legacy snake_case and the more modern camelCase. I'm using a pattern that tries to decode camelCase first then falls back to snake_case. To do this, I just create a snake case and camel case version of the same case classes. Each case class has a method to convert to the other type (e.g. toSnakeCase or toCamelCase.) Then add codecs like:

given associationScDecoder: Decoder[AssociationSC] = deriveDecoder
given associationScEncoder: Encoder[AssociationSC] = deriveEncoder
private val associationCcDecoder: Decoder[Association] = deriveDecoder
given associationEncoder: Encoder[Association] = deriveEncoder
given associationDecoder: Decoder[Association] = associationCcDecoder or associationScDecoder.map(_.toCamelCase)

For this to work, the snake case and camel case need to have a required field not found in the other.