Is there a proper way of changing all the values of the fields with a given key using Scala Circe?
I have n fields named validTo
as dates (e.g. "2024-09-12T00:00:00.000+00:00"
) and I need to shift those dates by a given number of days, so I need a way to find and replace all those validTo
fields in that JSON.
IMPORTANT NOTE: I don't know the JsonPath of those fields in advance, but only their key (name).
I see that there is a function, inspired by Play framework, to find all the values of a given key in Circe: io.circe.Json#findAllByKey
:
/**
* Recursively return all values matching the specified `key`.
*
* The Play docs, from which this method was inspired, reads:
* "Lookup for fieldName in the current object and all descendants."
*/
final def findAllByKey(key: String): List[Json]
I will use something like this:
//> using dep io.circe::circe-parser::0.14.6
//> using dep org.typelevel::cats-core::2.10.0
import scala.util.*
import io.circe.*, io.circe.parser.*
import java.time.LocalDate
import java.time.format.DateTimeFormatter
val rawJson: String = """
{
"foo": "bar",
"validTo": "2020-10-11",
"list of stuff": {
"validTo": "2020-10-10",
"pippo": 2
}
}
"""
val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
def parseDate(s: String): String = Try(LocalDate.parse(s, formatter)) match
case Success(date) => formatter.format(date.plusDays(10))
case Failure(_) => s
def alter(json: Json): Json = json
.mapArray(_.map(alter))
.mapObject(obj =>
JsonObject.fromIterable(obj.toList.map {
case ("validTo", x) => ("validTo", x.mapString(parseDate))
case (x, j) => (x, j.foldWith(this))
})
)
object circe extends App:
val json = parse(rawJson).getOrElse(Json.Null)
println(alter(json).spaces2SortKeys)
where in the parseDate
function you can handle as you want the date case, maybe even throwing exception
Alternatively, you can follow what @Daenyth said and use a Folder[json]
:
val folder = new Json.Folder[Json] {
def onNull: Json = Json.Null
def onBoolean(value: Boolean): Json = Json.fromBoolean(value)
def onNumber(value: JsonNumber): Json = Json.fromJsonNumber(value)
def onString(value: String): Json = Json.fromString(value)
def onArray(value: Vector[Json]): Json =
Json.fromValues(value.map(_.foldWith(this)))
def onObject(value: JsonObject): Json =
Json.fromJsonObject(
JsonObject.fromIterable(value.toList.map {
case ("validTo", x) => ("validTo", x.mapString(parseDate))
case (x, j) => (x, alter(j))
})
)
}
def alterFold(json:Json) = json.foldWith(folder)