app/utils/Circe.scala (50 lines of code) (raw):
package utils
import io.circe.{Decoder, Json, JsonObject, Printer}
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import scala.jdk.CollectionConverters._
object Circe {
def decodeStringAndCollect[T](pf: PartialFunction[String,T]): Decoder[T] = Decoder.decodeString.emap { s =>
pf.lift(s)
.map(Right.apply)
.getOrElse(Left(s"Unexpected value: $s"))
}
private val printer = Printer.spaces2.copy(dropNullValues = true)
def noNulls(json: Json): String = printer.print(json)
// Converts Circe Json to Dynamodb Attributes
def jsonToDynamo(json: Json): AttributeValue =
json.fold(
jsonNull = AttributeValue.builder().nul(true).build,
jsonBoolean = bool => AttributeValue.builder.bool(bool).build,
jsonNumber = n => AttributeValue.builder.n(n.toString).build,
jsonString = s => AttributeValue.builder.s(s).build,
jsonArray = arr => AttributeValue.builder.l(arr.map(jsonToDynamo).asJava).build,
jsonObject = obj => {
val map = obj.toMap.view.mapValues(jsonToDynamo).toMap
AttributeValue.builder.m(map.asJava).build
}
)
// Converts Dynamodb Attributes to Circe Json
def dynamoToJson(attribute: AttributeValue): Json = {
if (attribute.hasM()) {
// Map
dynamoMapToJson(attribute.m())
} else if (attribute.hasL()) {
// List
Json.fromValues(attribute.l().asScala.map(dynamoToJson))
} else if (attribute.hasSs()) {
// Set of strings
Json.fromValues(attribute.ss().asScala.map(Json.fromString))
} else if (attribute.s() != null) {
// String
Json.fromString(attribute.s())
} else if (attribute.n() != null) {
// Number
Json.fromDouble(attribute.n().toDouble).getOrElse(Json.Null)
} else if (attribute.bool() != null) {
// Bool
Json.fromBoolean(attribute.bool())
} else {
Json.Null
}
}
def dynamoMapToJson(item: java.util.Map[String, AttributeValue]): Json = {
val jsonMap: Map[String, Json] = item
.asScala
.view
.mapValues(dynamoToJson)
.toMap
Json.fromJsonObject(JsonObject.fromMap(jsonMap))
}
}