Skip to content

Instantly share code, notes, and snippets.

@odenzo
Last active February 27, 2022 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save odenzo/98efe3bda57ed95e8e5595730815aeca to your computer and use it in GitHub Desktop.
Save odenzo/98efe3bda57ed95e8e5595730815aeca to your computer and use it in GitHub Desktop.
trait CirceUtils {
val unCaptialize: String => String = s =>
if s == null then null
else
s.headOption match {
case Some(v) if v.isUpper => s.drop(1).prepended(v.toLower)
case _ => s
}
val capitalize: String => String = (s: String) => s.capitalize
def mapKeys(mapping: Map[String, String])(s: String): String = mapping.get(s).fold(s)(n => n)
/** Make sure this is done eagerly, the inverse used for changing from Json Key Names to case class field names */
def reverse(mapping: Map[String, String]): Map[String, String] = {
assert(mapping.values.toSet.size == mapping.size, "There should be no duplicate values in table")
mapping.toList.map(_.swap).to(Map)
}
/** Applies fn to all keys in the Json which should be a JsonObject */
def transformKeys(fn: String => String)(obj: Json): Json =
obj.mapObject { o => JsonObject.fromIterable(o.toIterable.map((k, v) => fn(k) -> v)) }
def prepareKeys(fn: String => String)(cursor: ACursor): ACursor =
cursor.withFocus(transformKeys(fn))
def encoderTransformKey(fn: String => String)(obj: JsonObject): JsonObject =
transformKeys(fn)
.compose(Json.fromJsonObject)
.andThen(json => json.asObject.get)
.apply(obj)
}
An Example Codec with upcase or downcase:
case class Foo(aaa: String, bbbb: Int, c: Boolean)
object Foo:
val decoder: Decoder[Foo] = deriveDecoder[Foo].prepare(prepareKeys(unCaptialize))
val encoder: Encoder.AsObject[Foo] = deriveEncoder[Foo].mapJsonObject(encoderTransformKey(capitalize))
val codec: Codec.AsObject[Foo] = Codec.AsObject.from(decoder, encoder)
And one to rename:
case class Bar(aaa: String, bbbb: Int, c: Boolean)
object Bar:
val rename: Map[String, String] = Map("aaa" -> "TheFirst", "bbbb" -> "TheSecond", "cIsNotMapped" -> "IsItTrue", "noSuchKey" -> "ERROR")
val decoder: Decoder[Bar] = deriveDecoder[Bar].prepare(prepareKeys(mapKeys(reverse(rename))))
val encoder: Encoder.AsObject[Bar] = deriveEncoder[Bar].mapJsonObject(encoderTransformKey(mapKeys(rename)))
val codec: Codec.AsObject[Bar] = Codec.AsObject.from(decoder, encoder)
Note that not all fields need to be renamed, and the renaming Map can have extra fields w/o error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment