I am mapping some xml to a case class and it works fine, but I have a feeling that my imperative blinkers are blinding me to a better functional solution. Can anyone suggest a better way than this:
def buildAddress(geocodeResponse: NodeSeq) : Address = {
val addressNodes = geocodeResponse \\ "address_component"
var street = " "
var town = ""
var suburb = ""
var province = ""
var country = ""
var postalCode = ""
addressNodes.foreach {node =>
val typeString = (node \ "type").head.text
if ("street_number" == typeString) {
street = (node \ "long_name").text + street
}
else if ("route" == typeString) {
street = street + (node \ "long_name").text
}
else if ("locality" == typeString) {
town = (node \ "long_name").text
}
else if ("sublocality" == typeString) {
suburb = (node \ "long_name").text
}
else if ("administrative_area_level_1" == typeString) {
province = (node \ "long_name").text
}
else if ("country" == typeString) {
country = (node \ "long_name").text
}
else if ("postal_code" == typeString) {
town = (node \ "long_name").text
}
}
Address(street,suburb,town,province,country,postalCode)
}
The xml in this case is retrieved from he Google geo-coding api, using Dispatch.
import dispatch._
def lookupAddress(lat: Double, long: Double): Address = {
val position = lat.toString + "," + long.toString
val req = url("http://maps.googleapis.com/maps/api/geocode/xml") <<? Map("latlng" -> position, "sensor" -> "true")
Http(req </> {
nodes => buildAddress(nodes)
})
}
The xml result looks like this:
<GeocodeResponse>
<status>OK</status>
<result>
<type>street_address</type>
<formatted_address>3 Louw St, Stellenbosch 7600, South Africa</formatted_address>
<address_component>
<long_name>3</long_name>
<short_name>3</short_name>
<type>street_number</type>
</address_component>
<address_component>
<long_name>Louw St</long_name>
<short_name>Louw St</short_name>
<type>route</type>
</address_component>
<address_component>
<long_name>Stellenbosch Central</long_name>
<short_name>Stellenbosch Central</short_name>
<type>sublocality</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>locality</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>administrative_area_level_3</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<address_component>
<long_name>7600</long_name>
<short_name>7600</short_name>
<type>postal_code</type>
</address_component>
<geometry>
<location>
<lat>-33.9403990</lat>
<lng>18.8610090</lng>
</location>
<location_type>ROOFTOP</location_type>
<viewport>
<southwest>
<lat>-33.9435466</lat>
<lng>18.8578614</lng>
</southwest>
<northeast>
<lat>-33.9372514</lat>
<lng>18.8641566</lng>
</northeast>
</viewport>
</geometry>
</result>
<result>
<type>sublocality</type>
<type>political</type>
<formatted_address>Stellenbosch Central, Stellenbosch, South Africa</formatted_address>
<address_component>
<long_name>Stellenbosch Central</long_name>
<short_name>Stellenbosch Central</short_name>
<type>sublocality</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>locality</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>administrative_area_level_3</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.9354048</lat>
<lng>18.8640607</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-33.9437180</lat>
<lng>18.8449199</lng>
</southwest>
<northeast>
<lat>-33.9230960</lat>
<lng>18.8778929</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-33.9437180</lat>
<lng>18.8449199</lng>
</southwest>
<northeast>
<lat>-33.9230960</lat>
<lng>18.8778929</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>postal_code</type>
<formatted_address>7599, South Africa</formatted_address>
<address_component>
<long_name>7599</long_name>
<short_name>7599</short_name>
<type>postal_code</type>
</address_component>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.9300286</lat>
<lng>18.8640607</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-33.9693080</lat>
<lng>18.8019200</lng>
</southwest>
<northeast>
<lat>-33.8700550</lat>
<lng>18.9232900</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-33.9693080</lat>
<lng>18.8019200</lng>
</southwest>
<northeast>
<lat>-33.8700550</lat>
<lng>18.9232900</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>locality</type>
<type>political</type>
<formatted_address>Stellenbosch, South Africa</formatted_address>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>locality</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>administrative_area_level_3</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.9366667</lat>
<lng>18.8613889</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-34.0150869</lat>
<lng>18.7658819</lng>
</southwest>
<northeast>
<lat>-33.8782960</lat>
<lng>18.9232900</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-34.0150869</lat>
<lng>18.7658819</lng>
</southwest>
<northeast>
<lat>-33.8782960</lat>
<lng>18.9232900</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>postal_code</type>
<formatted_address>7600, South Africa</formatted_address>
<address_component>
<long_name>7600</long_name>
<short_name>7600</short_name>
<type>postal_code</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry/>
</result>
<result>
<type>administrative_area_level_3</type>
<type>political</type>
<formatted_address>Stellenbosch, South Africa</formatted_address>
<address_component>
<long_name>Stellenbosch</long_name>
<short_name>Stellenbosch</short_name>
<type>administrative_area_level_3</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.9405478</lat>
<lng>18.9502232</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-34.0633899</lat>
<lng>18.7083300</lng>
</southwest>
<northeast>
<lat>-33.7933599</lat>
<lng>19.2438000</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-34.0633899</lat>
<lng>18.7083300</lng>
</southwest>
<northeast>
<lat>-33.7933599</lat>
<lng>19.2438000</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>administrative_area_level_2</type>
<type>political</type>
<formatted_address>Brede River DC, South Africa</formatted_address>
<address_component>
<long_name>Brede River DC</long_name>
<short_name>Brede River DC</short_name>
<type>administrative_area_level_2</type>
<type>political</type>
</address_component>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.4220698</lat>
<lng>19.7591675</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-34.1172599</lat>
<lng>18.7083299</lng>
</southwest>
<northeast>
<lat>-32.1844899</lat>
<lng>21.0103399</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-34.1172599</lat>
<lng>18.7083299</lng>
</southwest>
<northeast>
<lat>-32.1844899</lat>
<lng>21.0103399</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>administrative_area_level_1</type>
<type>political</type>
<formatted_address>Western Cape, South Africa</formatted_address>
<address_component>
<long_name>Western Cape</long_name>
<short_name>WC</short_name>
<type>administrative_area_level_1</type>
<type>political</type>
</address_component>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-33.2277918</lat>
<lng>21.8568586</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-34.8330538</lat>
<lng>17.7575638</lng>
</southwest>
<northeast>
<lat>-30.4302599</lat>
<lng>24.2224100</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-34.8330538</lat>
<lng>17.7575638</lng>
</southwest>
<northeast>
<lat>-30.4302599</lat>
<lng>24.2224100</lng>
</northeast>
</bounds>
</geometry>
</result>
<result>
<type>country</type>
<type>political</type>
<formatted_address>South Africa</formatted_address>
<address_component>
<long_name>South Africa</long_name>
<short_name>ZA</short_name>
<type>country</type>
<type>political</type>
</address_component>
<geometry>
<location>
<lat>-30.5594820</lat>
<lng>22.9375060</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>-34.9670000</lat>
<lng>16.2817000</lng>
</southwest>
<northeast>
<lat>-22.1253869</lat>
<lng>33.0469000</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>-34.9670000</lat>
<lng>16.2817000</lng>
</southwest>
<northeast>
<lat>-22.1253869</lat>
<lng>33.0469000</lng>
</northeast>
</bounds>
</geometry>
</result>
</GeocodeResponse>
Not really more functional, but a match cleans things up a bit. Given you have to look for specific strings, and the rules for dealing with the value depends on the string, I'm not sure how much more "functional" it can be.
def buildAddress(geocodeResponse: NodeSeq) : Address = {
val addressNodes = geocodeResponse \\ "address_component"
var street = " "
var town = ""
var suburb = ""
var province = ""
var country = ""
var postalCode = ""
addressNodes.foreach {node =>
lazy val text = (node \ "long_name").text // lazy so we don't look until we know it should be there
(node \ "type").head.text match {
case "street_number" => street = text + street
case "route" => street = street + text
case "locality" => town = text
case "sublocality" => suburb = text
case "administrative_area_level_1" => province = text
case "country" => country = text
case "postal_code" => town = text
case _ => // Hmm. Not sure what is expected here.
}
}
Address(street,suburb,town,province,country,postalCode)
}
EDIT:
I'm not sure how much more "functional" it can be.
But then again (note - this is again untested, I need to debug the getComponent, but the idea is clear, I hope. Corrections may arrive later)
def buildAddress(geocodeResponse: NodeSeq) : Address = {
val addressNodes = geocodeResponse \\ "address_component"
def getComponent(word:String) = {
addressNodes.find{_ \ "type".head.text == word) match {
case Some(node) => node \ "long_name".text
case _=> ""
}
}
Address(getComponent("street_number")+getComponent("route"),
getComponent("suburb"),
getComponent("town"),
getComponent("province"),
getComponent("country"),
getComponent("postalCode"))
}
Unless you get some kind of reflection, there really isn't much you can do to make it better. It seems you could improve how you match your attributes, doing something like this, to help with the sheer drudgery of it:
def buildAddress(geocodeResponse: NodeSeq) : Address = {
def getComponent(component: String): String = {
val longNames = for {
addressComponent <- geocodeResponse \\ "address_component"
componentType <- addressComponent \ "type"
if componentType.text == component
} yield addressComponent \ "long_name"
longNames.head.text
}
implicit def toC(s: String) = new { def c = getComponent(s) }
implicit val map = Map(
'street -> ("route".c + "street_number".c),
'town -> "locality".c,
'suburb -> "sublocality".c,
'province -> "administrative_area_level_1".c,
'country -> "country".c,
'postalCode -> "postal_code".c
)
Address('street, 'suburb, 'town, 'province, 'country, 'postalCode)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With