Reputation: 3210
I am trying to read JSON in following format
[
{type: "city",name:"Paris",lan:10.1,lot:20.0},
{type: "country",name:"France",...}
]
to Array[GeoObject] where GeoObject is parent for Country and City classes
how can I do this trick with Json Readers ? I have tryed something like
def mkObject(..,type:String,..):GeoObject = type match {
case 'city' => City(...)
case 'country' => Country(...)
}
implicit val geoobjectReads = (
(JsPath \ "name").read[String] and
....
)(mkObject _)
but without success.
And actually I have the other different question.
I have in JSON two fields 'lan','lot' - but GeoObject has only one
coordinates:LanLot
where LanLot is just case class with 2 arguments.
Upvotes: 0
Views: 310
Reputation: 403
I'm not a huge fan of Play! json read syntax. So I would prefer to use Json Inception to read into temporary case class, and then I would convert from one case class to another in the code explicitly.
Something like this. I replaced type
with typ
, to avoid code pollution with backticks.
import play.api.libs.json.Json
// let's just define all the structures you need
case class Coordinates(latitude: Double, longitude: Double)
abstract class GeoObject {
def typ: String
def name: String
def coordinates: Coordinates
}
val CityType = "city"
val CountryType = "country"
case class City(name: String, coordinates: Coordinates) extends GeoObject {
val typ = CityType
}
case class Country(name: String, coordinates: Coordinates) extends GeoObject {
val typ = CountryType
}
// case class representing json
case class GeoObjectJson(typ: String, name: String, lat: Double, long: Double)
implicit val geoObjectJsonReads = Json.reads[GeoObjectJson]
// companion object (as a factory)
object GeoObject {
def apply(geoJson: GeoObjectJson): GeoObject = {
val coord = Coordinates(geoJson.lat, geoJson.long)
geoJson.typ match {
case CityType => City(geoJson.name, coord)
case CountryType => Country(geoJson.name, coord)
}
}
}
// tests
val cityJsonString = """{"typ":"city", "name": "New York", "lat": 40.77, "long": -73.92}"""
val cityJson = Json.parse(cityJsonString).as[GeoObjectJson]
val cityGeoObject = GeoObject(cityJson)
// City(New York,Coordinates(40.77,-73.92))
val countryJsonString = """{"typ":"country", "name": "Canada", "lat": 40.77, "long": -73.92}"""
val countryJson = Json.parse(countryJsonString).as[GeoObjectJson]
val countryGeoObject = GeoObject(countryJson)
// Country(Canada,Coordinates(40.77,-73.92))
Upvotes: 0
Reputation: 55569
You'll also want to make json reads/writes for your coordinates if you want them in a separate case class, and they can be parsed as a nested object inside GeoObject
. You were definitely on the right track, though.
Putting it all together:
case class Coordinates(latitude: Double, longitude: Double)
object Coordinates {
implicit val jsonReads: Reads[Coordinates] = (
(__ \ "latitude").read[Double] and
(__ \ "longitude").read[Double]
)(Coordinates.apply _)
}
class GeoObject(`type`: String, name: String, coordinates: Coordinates)
object GeoObject {
def apply(`type`: String, name: String, coordinates: Coordinates) = {
`type` match {
case "city" => City(`type`, name, coordinates)
case "country" => Country(`type`, name, coordinates)
}
}
implicit val jsonReads: Reads[GeoObject] = (
(__ \ "type").read[String] and
(__ \ "name").read[String] and
(__ \ "coordinates").read[Coordinates]
)(GeoObject.apply _)
}
case class City(`type`: String, name: String, coordinates: Coordinates) extends GeoObject(`type`, name, coordinates)
case class Country(`type`: String, name: String, coordinates: Coordinates) extends GeoObject(`type`, name, coordinates)
And in action (play console
):
scala> Json.parse("""{"type":"city", "name": "New York", "coordinates": {"latitude": 40.77, "longitude": -73.92}}""").as[GeoObject]
res2: GeoObject = City(city,New York,Coordinates(40.77,-73.92))
I overrode GeoObject.apply
to always convert to City
or Country
, but you can use whatever function you want.
Edit: I went ahead and used type
with backticks as Vikas pointed out.
Upvotes: 2