I’ve just revisited that thread and your code, and I still cannot really understand your complaints. I still like my spray-json/lenses approach a bit better, and I probably would do a few things different than you with circe, but I don’t think that the HCursor
API is that terrible. I’m really wondering how an Any
-based implementation should fare significantly better.
In case you’re curious to try it out, here’s a naive conversion from circe Json
to Map[String, Any]
:
def toUntyped(json: Json): Option[Map[String, Any]] = {
def cnv(j: Json): Any =
j.fold(
null,
identity,
_.toDouble,
identity,
_.toList.map(cnv),
cnvObj
)
def cnvObj(jo: JsonObject): Map[String, Any] =
jo.toMap.view.mapValues(cnv).toMap
json.asObject.map(cnvObj)
}
Another thing is that your use case is somewhat special, though not completely unusual. You are reading the JSON data while transforming/extracting at the same time. This is perfectly fine, but the more common case is that one really wants to map JSON data to an equivalent representation in the code. If we take this route, the picture is somewhat different.
import io.circe.generic.auto._
sealed trait Geometry
case class Polygon(coordinates: List[List[List[Double]]]) extends Geometry
case class MultiPolygon(coordinates: List[List[List[List[Double]]]]) extends Geometry
case class Properties(name: String)
case class Feature(properties: Properties, geometry: Geometry)
case class FeatureCollection(features: List[Feature])
implicit val decodeGeometry: Decoder[Geometry] =
(c: HCursor) =>
c.downField("type").as[String].flatMap {
case "Polygon" => c.as[Polygon]
case "MultiPolygon" => c.as[MultiPolygon]
}
val features = parse(jsonStr).flatMap(_.as[FeatureCollection])
circe in particular is pretty much opiniated in that regard, as expressed in the design document:
You generally shouldn’t need or want to work with JSON ASTs directly.
Once you have this direct representation of the JSON model, it should be much easier to transform it into the representation your application actually wants, i.e. Map[String, List[List[Location]]]
. This is certainly less efficient than the direct transformation upon parsing - on the other hand now you have the full content of the JSON data available in a nicely typed fashion for whatever else you may want to repurpose it to.