My problem is basically to deserialize a complex json string to an object.
This is the case classes with implicits:
import spray.json.{RootJsonFormat, _}
case class OpenCageDataDocumentation(value: String) extends AnyVal
case class OpenCageDataLicence(name: String, url: String)
case class OpenCageDataRate(limit: Double, remaining: Double, reset: Double)
case class OpenCageDataStatus(code: Int, message: String)
case class OpenCageDataStayInformed(blog: String, twitter: String)
case class OpenCageDataTimestamp(created_http: String, created_unix: Long)
case class OpenCageResultGeometry(lat: Double, lng: Double)
case class OpenCageResultAnnotation(DMS: Map[String, String],
FIPS: Map[String, String],
MGRS: String,
Maidenhead: String,
Mercator: Map[String, Double],
OSM: Map[String, String],
UN_M49: Map[String, Either[Map[String, String], List[String]]],
callingcode: Long,
currency: Map[String, Either[String, Either[Double, List[String]]]],
flag: String,
geohash: String,
qibla: Double,
roadinfo: Map[String, String],
sun: Map[String, Map[String, Double]],
timezone: Map[String, Either[String, Double]],
what3words: Map[String, String])
case class OpenCageDataResult(geometry: Option[OpenCageResultGeometry],
annotations: Map[String, OpenCageResultAnnotation],
confidence: Long,
formatted: String,
components: Map[String, String],
bounds: Map[String, OpenCageResultGeometry])
case class OpenCageDataResponse(documentation: String,
licenses: List[OpenCageDataLicence],
rate: OpenCageDataRate,
results: List[OpenCageDataResult],
status: OpenCageDataStatus,
stay_informed: OpenCageDataStayInformed,
thanks: String,
timestamp: OpenCageDataTimestamp,
total_results: Int)
trait OpenCageJsonFormatResponse extends DefaultJsonProtocol {
implicit val jsonFormatResponse: RootJsonFormat[OpenCageDataResponse] = jsonFormat9(OpenCageDataResponse)
implicit val jsonFormatLicence: RootJsonFormat[OpenCageDataLicence] = jsonFormat2(OpenCageDataLicence)
implicit val jsonFormatRate: RootJsonFormat[OpenCageDataRate] = jsonFormat3(OpenCageDataRate)
implicit val jsonFormatStatus: RootJsonFormat[OpenCageDataStatus] = jsonFormat2(OpenCageDataStatus)
implicit val jsonFormatStayInformed: RootJsonFormat[OpenCageDataStayInformed] = jsonFormat2(OpenCageDataStayInformed)
implicit val jsonFormatTimestamp: RootJsonFormat[OpenCageDataTimestamp] = jsonFormat2(OpenCageDataTimestamp)
implicit val jsonFormatGeometry: RootJsonFormat[OpenCageResultGeometry] = jsonFormat2(OpenCageResultGeometry)
implicit val jsonFormatResult: RootJsonFormat[OpenCageDataResult] = jsonFormat6(OpenCageDataResult)
implicit val jsonFormatResultAnnotation: RootJsonFormat[OpenCageResultAnnotation] = jsonFormat16(OpenCageResultAnnotation)
}
This is the main object:
import scalaj.http.{Http, HttpResponse}
import spray.json._
object testapi2 extends OpenCageJsonFormatResponse {
private final val BASE_OPEN_CAGE_DATA_URI = s"https://api.opencagedata.com/geocode/v1/json?"
private final val OPEN_CAGE_DATA_API_KEY = sys.env("OPEN_CAGE_DATA_API_KEY")
def main(args: Array[String]): Unit = {
val response: HttpResponse[String] = Http(BASE_OPEN_CAGE_DATA_URI)
.param("q", "500 W 120 S")
.param("key", OPEN_CAGE_DATA_API_KEY)
.execute()
val json = response.body.parseJson;
println(json)
val parsedBody = json.convertTo[OpenCageDataResponse]
println(parsedBody)
}
}
It breaks on line val parsedBody = json.convertTo[OpenCageDataResponse]
with the following trace:
Exception in thread "main" java.lang.NullPointerException
at spray.json.JsValue.convertTo(JsValue.scala:33)
at spray.json.CollectionFormats$$anon$1.$anonfun$read$1(CollectionFormats.scala:30)
at scala.collection.Iterator$$anon$10.next(Iterator.scala:459)
at scala.collection.Iterator.foreach(Iterator.scala:941)
at scala.collection.Iterator.foreach$(Iterator.scala:941)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1429)
at scala.collection.generic.Growable.$plus$plus$eq(Growable.scala:62)
at scala.collection.generic.Growable.$plus$plus$eq$(Growable.scala:53)
at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:184)
at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:47)
at scala.collection.TraversableOnce.to(TraversableOnce.scala:315)
at scala.collection.TraversableOnce.to$(TraversableOnce.scala:313)
at scala.collection.AbstractIterator.to(Iterator.scala:1429)
at scala.collection.TraversableOnce.toList(TraversableOnce.scala:299)
at scala.collection.TraversableOnce.toList$(TraversableOnce.scala:299)
at scala.collection.AbstractIterator.toList(Iterator.scala:1429)
at spray.json.CollectionFormats$$anon$1.read(CollectionFormats.scala:30)
at spray.json.CollectionFormats$$anon$1.read(CollectionFormats.scala:27)
at spray.json.ProductFormats.fromField(ProductFormats.scala:58)
at spray.json.ProductFormats.fromField$(ProductFormats.scala:51)
at test.scala$.fromField(scala.scala:20)
at spray.json.ProductFormatsInstances$$anon$9.read(ProductFormatsInstances.scala:262)
at spray.json.ProductFormatsInstances$$anon$9.read(ProductFormatsInstances.scala:245)
at spray.json.JsValue.convertTo(JsValue.scala:33)
at test.scala$.main(scala.scala:36)
at test.scala.main(scala.scala)
And this is the actual json object coming back as a response
{
"documentation": "https://opencagedata.com/api",
"licenses": [
{
"name": "see attribution guide",
"url": "https://opencagedata.com/credits"
}
],
"rate": {
"limit": 2500,
"remaining": 2493,
"reset": 1628899200
},
"results": [
{
"annotations": {
"DMS": {
"lat": "40° 45' 59.93748'' N",
"lng": "111° 54' 20.11104'' W"
},
"FIPS": {
"county": "49035",
"state": "49"
},
"MGRS": "12TVL2357013247",
"Maidenhead": "DN40bs13hx",
"Mercator": {
"x": -12457272.898,
"y": 4950075.893
},
"OSM": {
"edit_url": "https://www.openstreetmap.org/edit?way=134275081#map=17/40.76665/-111.90559",
"note_url": "https://www.openstreetmap.org/note/new#map=17/40.76665/-111.90559&layers=N",
"url": "https://www.openstreetmap.org/?mlat=40.76665&mlon=-111.90559#map=17/40.76665/-111.90559"
},
"UN_M49": {
"regions": {
"AMERICAS": "019",
"NORTHERN_AMERICA": "021",
"US": "840",
"WORLD": "001"
},
"statistical_groupings": [
"MEDC"
]
},
"callingcode": 1,
"currency": {
"alternate_symbols": [
"US$"
],
"decimal_mark": ".",
"disambiguate_symbol": "US$",
"html_entity": "$",
"iso_code": "USD",
"iso_numeric": "840",
"name": "United States Dollar",
"smallest_denomination": 1,
"subunit": "Cent",
"subunit_to_unit": 100,
"symbol": "$",
"symbol_first": 1,
"thousands_separator": ","
},
"flag": "",
"geohash": "9x0rvt2ffy4zsqbc414g",
"qibla": 28.5,
"roadinfo": {
"drive_on": "right",
"road": "500 West",
"speed_in": "mph"
},
"sun": {
"rise": {
"apparent": 1628858280,
"astronomical": 1628852040,
"civil": 1628856480,
"nautical": 1628854320
},
"set": {
"apparent": 1628821560,
"astronomical": 1628827800,
"civil": 1628823360,
"nautical": 1628825460
}
},
"timezone": {
"name": "America/Denver",
"now_in_dst": 1,
"offset_sec": -21600,
"offset_string": "-0600",
"short_name": "MDT"
},
"what3words": {
"words": "means.detect.taxi"
}
},
"bounds": {
"northeast": {
"lat": 40.7666993,
"lng": -111.9055364
},
"southwest": {
"lat": 40.7665993,
"lng": -111.9056364
}
},
"components": {
"ISO_3166-1_alpha-2": "US",
"ISO_3166-1_alpha-3": "USA",
"_category": "commerce",
"_type": "retail",
"city": "Salt Lake City",
"continent": "North America",
"country": "United States",
"country_code": "us",
"county": "Salt Lake County",
"house_number": "120",
"postcode": "84101",
"retail": "North",
"road": "500 West",
"state": "Utah",
"state_code": "UT"
},
"confidence": 9,
"formatted": "North, 120 500 West, Salt Lake City, UT 84101, United States of America",
"geometry": {
"lat": 40.7666493,
"lng": -111.9055864
}
},
{
"annotations": {
"DMS": {
"lat": "40° 53' 18.11256'' N",
"lng": "111° 53' 32.41824'' W"
},
"FIPS": {
"county": "49011",
"state": "49"
},
"MGRS": "12TVL2482626747",
"Maidenhead": "DN40bv23we",
"Mercator": {
"x": -12455798.136,
"y": 4967913.265
},
"OSM": {
"edit_url": "https://www.openstreetmap.org/edit?way=684890599#map=17/40.88836/-111.89234",
"note_url": "https://www.openstreetmap.org/note/new#map=17/40.88836/-111.89234&layers=N",
"url": "https://www.openstreetmap.org/?mlat=40.88836&mlon=-111.89234#map=17/40.88836/-111.89234"
},
"UN_M49": {
"regions": {
"AMERICAS": "019",
"NORTHERN_AMERICA": "021",
"US": "840",
"WORLD": "001"
},
"statistical_groupings": [
"MEDC"
]
},
"callingcode": 1,
"currency": {
"alternate_symbols": [
"US$"
],
"decimal_mark": ".",
"disambiguate_symbol": "US$",
"html_entity": "$",
"iso_code": "USD",
"iso_numeric": "840",
"name": "United States Dollar",
"smallest_denomination": 1,
"subunit": "Cent",
"subunit_to_unit": 100,
"symbol": "$",
"symbol_first": 1,
"thousands_separator": ","
},
"flag": "",
"geohash": "9x22tg6rzx97d57jjnu3",
"qibla": 28.49,
"roadinfo": {
"drive_on": "right",
"road": "500 West",
"speed_in": "mph"
},
"sun": {
"rise": {
"apparent": 1628858220,
"astronomical": 1628851980,
"civil": 1628856480,
"nautical": 1628854320
},
"set": {
"apparent": 1628821560,
"astronomical": 1628827800,
"civil": 1628823360,
"nautical": 1628825520
}
},
"timezone": {
"name": "America/Denver",
"now_in_dst": 1,
"offset_sec": -21600,
"offset_string": "-0600",
"short_name": "MDT"
},
"what3words": {
"words": "riding.grass.supply"
}
},
"bounds": {
"northeast": {
"lat": 40.8884146,
"lng": -111.8922884
},
"southwest": {
"lat": 40.8883146,
"lng": -111.8923884
}
},
"components": {
"ISO_3166-1_alpha-2": "US",
"ISO_3166-1_alpha-3": "USA",
"_category": "building",
"_type": "building",
"continent": "North America",
"country": "United States",
"country_code": "us",
"county": "Davis County",
"house_number": "120",
"postcode": "84010",
"road": "500 West",
"state": "Utah",
"state_code": "UT",
"town": "Bountiful"
},
"confidence": 10,
"formatted": "120 500 West, Bountiful, UT 84010, United States of America",
"geometry": {
"lat": 40.8883646,
"lng": -111.8923384
}
},
{
"annotations": {
"DMS": {
"lat": "15° 30' 0.00000'' S",
"lng": "35° 0' 0.00000'' E"
},
"MGRS": "36LYH1454485369",
"Maidenhead": "KH74mm00aa",
"Mercator": {
"x": 3896182.178,
"y": -1735479.281
},
"OSM": {
"note_url": "https://www.openstreetmap.org/note/new#map=17/-15.50000/35.00000&layers=N",
"url": "https://www.openstreetmap.org/?mlat=-15.50000&mlon=35.00000#map=17/-15.50000/35.00000"
},
"UN_M49": {
"regions": {
"AFRICA": "002",
"EASTERN_AFRICA": "014",
"MW": "454",
"SUB-SAHARAN_AFRICA": "202",
"WORLD": "001"
},
"statistical_groupings": [
"LDC",
"LEDC",
"LLDC"
]
},
"callingcode": 265,
"currency": {
"alternate_symbols": [],
"decimal_mark": ".",
"format": "%n %u",
"html_entity": "",
"iso_code": "MWK",
"iso_numeric": "454",
"name": "Malawian Kwacha",
"smallest_denomination": 1,
"subunit": "Tambala",
"subunit_to_unit": 100,
"symbol": "MK",
"symbol_first": 0,
"thousands_separator": ","
},
"flag": "",
"geohash": "kv0zu6q1zned3z8us7yj",
"qibla": 7.44,
"roadinfo": {
"drive_on": "left",
"speed_in": "km/h"
},
"sun": {
"rise": {
"apparent": 1628827140,
"astronomical": 1628822760,
"civil": 1628825820,
"nautical": 1628824260
},
"set": {
"apparent": 1628868660,
"astronomical": 1628873040,
"civil": 1628869980,
"nautical": 1628871540
}
},
"timezone": {
"name": "Africa/Maputo",
"now_in_dst": 0,
"offset_sec": 7200,
"offset_string": "+0200",
"short_name": "CAT"
},
"what3words": {
"words": "inventing.amiability.pastures"
}
},
"components": {
"ISO_3166-1_alpha-2": "MW",
"ISO_3166-1_alpha-3": "MWI",
"_category": "place",
"_type": "state",
"continent": "Africa",
"country": "Malawi",
"country_code": "mw",
"state": "Southern Region"
},
"confidence": 9,
"formatted": "Southern Region, Malawi",
"geometry": {
"lat": -15.5,
"lng": 35.0
}
}
],
"status": {
"code": 200,
"message": "OK"
},
"stay_informed": {
"blog": "https://blog.opencagedata.com",
"twitter": "https://twitter.com/OpenCage"
},
"thanks": "For using an OpenCage API",
"timestamp": {
"created_http": "Fri, 13 Aug 2021 18:18:10 GMT",
"created_unix": 1628878690
},
"total_results": 3
}
I don't really think that this is a good way of consuming objects in terms of shortness of a code, but I really didn't find anything better, and so appreciate for sharing a better approach to use further.