1

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.

zmerr
  • 534
  • 3
  • 18
  • 1
    I'd recommend to debug step by step each case class with a subset of the JSON. There's way too much data for us to spot it right away. – Gaël J Aug 14 '21 at 11:27
  • I was encountering lots of problems with serialization and deserialization of complex case class structures, to and from Json. If both the server and client sides are in Scala, and you don’t want the option of switching to another language later on, you can consider [java serialization](https://stackoverflow.com/questions/39369319/convert-any-type-in-scala-to-arraybyte-and-back) using input and output streams. You can also convert the byte array to String. For complex case class structures, it works likes a charm with no hassle and minimal code. – zmerr Aug 15 '21 at 07:52
  • If you need to use Json, though, you can as well look up `circe` or `zio-json` – zmerr Aug 15 '21 at 08:11
  • 2
    Ok, the solution is that you basically have to wrap to lazyFormat everything that has anything to do with the recursive json nature. implicit val fooFormat: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "i", "foo")) as written in the docs https://github.com/spray/spray-json section JsonFormats for recursive Types. There is a sentence about NPE either. All in all, if you have even a list of some objects you must wrap it into lazyFormat, otherwise NPE is guaranteed. – Ахтем Вейс Aug 15 '21 at 09:48

0 Answers0