zio-json
zio-json copied to clipboard
Extraction of single values
Ideally without decoding the whole structure, support cursor-like operations to extract single values.
This seems like an essential feature, and I'm surprised that it hasn't gotten more attention.
A common use case is looking for one or two interesting fields (which may be nested) in an otherwise large JSON document. An example is OpenWeatherMap Current Weather API, which returns a structure like
{
"coord": {
"lon": 10.99,
"lat": 44.34
},
"weather": [
{
"id": 501,
"main": "Rain",
"description": "moderate rain",
"icon": "10d"
}
],
"base": "stations",
"main": {
"temp": 298.48,
"feels_like": 298.74,
"temp_min": 297.56,
"temp_max": 300.05,
"pressure": 1015,
"humidity": 64,
"sea_level": 1015,
"grnd_level": 933
},
"visibility": 10000,
"wind": {
"speed": 0.62,
"deg": 349,
"gust": 1.18
},
"rain": {
"1h": 3.16
},
"clouds": {
"all": 100
},
"dt": 1661870592,
"sys": {
"type": 2,
"id": 2075663,
"country": "IT",
"sunrise": 1661834187,
"sunset": 1661882248
},
"timezone": 7200,
"id": 3163858,
"name": "Zocca",
"cod": 200
}
If all we are looking for is main.temp
, requiring the client to model this entire structure as a bunch of case classes (and potentially having to keep them up to date with any, even irrelevant, API changes) is very wasteful w.r.t. to developer time. Allowing for cursor-based decoding immensely helps with backwards-compatibility, as many API changes (especially structural ones) can be accounted for inside the custom Decoder
without invalidating the client-side data model.
In the absence of cursor support, is there a recommended workaround for dealing with this problem?
Thank you!
What you’ve asked for already is supported, but it will currently decode and allocate a complete AST for the input node.
See https://scastie.scala-lang.org/IWEQA9ogQoK2oQT73RlKEg
-
zio.json.ast.Json
is a Json AST (string, number, object, array, …) -
Json
has decoders and encoders -
Json#get
accepts aJsonCursor
Thank you @fsvehla, I really appreciate your time and a quick response! This does indeed address the use case.
Thanks @fsvehla but how that will go to extract an optional field from Json
. Do we have some getOpt
operator ?
After little effort found out like this we can pick optional
field
val fields = for {
ast <- source.fromJson[Json]
tempAst <- ast.get(JsonCursor.field("main").isObject.field("temp"))
optionalField <- ast.get(JsonCursor.field("competition")) match {
case Right(value) => value.as[Option[Int]]
case _ => Right(None)
}
temp <- tempAst.as[BigDecimal]
} yield (temp, optionalField)
fields match {
case Right(temp, optField) => println(s"$temp and $optField")
case _ => println("Nothing")
}