One of the main goals of Making Java Groovy is to show Java developers how much Groovy can make their lives easier. To that end, I just published a blog post (through Manning’s account) over a Java.net entitled, Groovy Weather: POGOs, Gson, and Open Weather. The blog post comes with a coupon code for 45% off the book. 🙂
(Spoiler: it’s “kousenjn“, but if you’re going to use it at least drop by the blog link.)
Another spoiler: it’s freakin’ cold outside. That’s partly what my blog post is about — consuming JSON data from Open Weather Map and displaying it at the console. That’s not terribly difficult, but the real value added comes from using Google’s Gson parser to convert the JSON objects into Groovy.
If you’re new to Groovy, the blog post shows a lot of detail. Regular readers of my blog, however, probably are at least comfortable with the language, so I thought I’d summarize the good parts here.
First, I need to show the JSON data, so I know what to map to.
(Er, “to what to map”? Ugh. Winston Churchill was once criticized for ending a sentence with a preposition. His reply was, “Madam, that is nonsense up with which I will not put.” That’s one of those possibly apocryphal stories that I don’t want to try to verify because I like it too much.)
Here’s the trivial Groovy expression to download the data:
[code language=”groovy”]
import groovy.json.*
String url = ‘http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct’
String jsonTxt = url.toURL().text
println JsonOutput.prettyPrint(jsonTxt)
[/code]
I’m using the static prettyPrint
method of JsonOutput
to make viewing the results easier. As a minor complaint, I have to mention that every time I use prettyPrint
, I’m surprised it doesn’t actually print. It just formats the JSON data. I still need to print the result. That seems rather misleading to me.
Anyway, here’s the result:
[code language=”javascript”]
{
"coord": {
"lon": -72.46,
"lat": 41.63
},
"sys": {
"message": 0.193,
"country": "United States of America",
"sunrise": 1389096985,
"sunset": 1389130583
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "Sky is Clear",
"icon": "01d"
}
],
"base": "gdps stations",
"main": {
"temp": 260.41,
"humidity": 33,
"pressure": 1025,
"temp_min": 258.71,
"temp_max": 262.15
},
"wind": {
"speed": 1.54,
"deg": 0
},
"clouds": {
"all": 0
},
"dt": 1389130569,
"id": 4835003,
"name": "Hartford",
"cod": 200
}
[/code]
The current temperature is buried inside the object, in the “temp” property of the “main” subobject. I could just parse this using a JsonSlurper
, but instead I decided to map the whole thing using Gson.
Gson wants a class structure that maps to the JSON hierarchy. Here’s mine, which I just stuffed into a single Groovy class called Model.groovy
.
[code language=”groovy”]
class Model {
Long dt
Long id
String name
Integer cod
Coordinates coord
Main main
System sys
Wind wind
Clouds clouds
Weather[] weather
}
class Main {
BigDecimal temp
BigDecimal humidity
BigDecimal pressure
BigDecimal temp_min
BigDecimal temp_max
}
class Coordinates {
BigDecimal lat
BigDecimal lon
String toString() { "($lat, $lon)" }
}
class Weather {
Integer id
String main
String description
String icon
}
class System {
String message
String country
Long sunrise
Long sunset
}
class Wind {
BigDecimal speed
BigDecimal deg
}
class Clouds {
BigDecimal all
}
[/code]
I added the data types based on reading the docs, which are pretty thin, and following the nested structure of the JSON data. The names of the classes aren’t important. It’s the property names that have to match the keys in the JSON maps for deserialization to work.
Using Gson is almost trivial. All I need is the fromJson
method in the Gson
class:
[code language=”groovy”]
import groovy.transform.*
import com.google.gson.Gson
@ToString(includeNames=true)
class Model {
…
}
String url = ‘http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct’
String jsonTxt = url.toURL().text
Gson gson = new Gson()
println gson.fromJson(jsonTxt, Model)
[/code]
Everything is converted to the Groovy class structure, just as expected.
Before printing the results, though, I need to do some data manipulation. If you looked at the current temperature value, you might have noticed it’s in Kelvin, of all things. As a US-based developer, I need to convert that to Fahrenheit.
[code language=”groovy”]
def convertTemp(temp) {
9 * (temp – 273.15) / 5 + 32
}
[/code]
The time fields are based on “Unix time”, which measures seconds in the current epoch (beginning January 1, 1970 GMT). Java’s Date
class has a constructor that takes a long
representing milliseconds from the beginning of the epoch.
[code language=”groovy”]
def convertTime(t) {
new Date(t*1000) // Unix time in sec, Java time in ms
}
[/code]
Finally, the wind speed is in “meters per second” and I want “miles per hour”. When I was in high school (and dinosaurs roamed the Earth), I learned about the Factor Label method, which meant I could memorize a single length conversion and calculate any other.
[code language=”groovy”]
def convertSpeed(mps) {
// 1 m/sec * 60 sec/min * 60 min/hr * 100 cm/m * 1 in/2.54 cm * 1 ft/12 in * 1 mi/5280 ft
mps * 60 * 60 * 100 / 2.54 / 12 / 5280
}
[/code]
I added all of these to the Model
class, and some getters to use them.
[code langauge=”groovy”]
class Model {
// … as before …
def getTime() { convertTime dt }
def getTemperature() { convertTemp main.temp }
def getLow() { Math.floor(convertTemp(main.temp_min)) }
def getHigh() { Math.ceil(convertTemp(main.temp_max)) }
def getSunrise() { convertTime sys.sunrise }
def getSunset() { convertTime sys.sunset }
def getSpeed() { convertSpeed wind.speed }
}
[/code]
Finally, here’s my nice, formatted toString method to print the results (also a part the Model
class):
[code language=”groovy”]
String toString() {
"""
Name : $name
Time : $time
Location : $coord
Weather : ${weather[0].main} (${weather[0].description})
Icon : http://openweathermap.org/img/w/${weather[0].icon}.png
Current Temp : $temperature F (high: $high F, low: $low F)
Humidity : ${main.humidity}%
Sunrise : $sunrise
Sunset : $sunset
Wind : $speed mph at ${wind.deg} deg
Cloudiness : ${clouds.all}%
"""
}
[/code]
I should mention that the Weather
attribute of the Model
class is a collection. Presumably that’s for when there are multiple weather stations associated with a given location. In the source code repository (linked below), I used Groovy’s each
method to iterate over them all. Here I’m just using the first one.
The driver for the system is:
[code language=”groovy”]
import com.google.gson.Gson
class OpenWeather {
String base = ‘http://api.openweathermap.org/data/2.5/weather?q=’
Gson gson = new Gson()
String getWeather(city=’Marlborough’, state=’CT’) {
String jsonTxt = "$base$city,$state".toURL().text
gson.fromJson(jsonTxt, Model).toString()
}
}
[/code]
I like Groovy’s default arguments for methods. If I invoke the getWeather
method without arguments (or as the weather
property in the usual idiom), then the result is for “Marlborough, CT”. Otherwise I supply a city and a state and they’re used instead.
Clearly this needs to be tested, or at least the converters so. Here’s a Spock test for them:
[code language=”groovy”]
import spock.lang.Specification
class ModelSpec extends Specification {
Model model = new Model()
def ‘convertTemp converts from Kelvin to F'() {
expect:
32 == model.convertTemp(273.15)
212 == model.convertTemp(373.15)
}
def ‘convertSpeed converts from meters/sec to miles/hour'() {
expect:
(2.23694 – model.convertSpeed(1)).abs() < 0.00001
}
def ‘convertTime converts from Unix time to java.util.Date'() {
given:
Calendar cal = Calendar.instance
cal.set(1992, Calendar.MAY, 5)
Date d = cal.time
long time = d.time / 1000 // Java time in ms, Unix time in sec
when:
Date date = model.convertTime(time)
then:
d – date < 1
}
}
[/code]
The mps to mph value I got from putting “1 meter per second in mph” into Google, which gave me the proper result.
Just to make sure the calls are working properly, here are a couple of tests for the Model
class.
[code language=”groovy”]
import spock.lang.Specification
class OpenWeatherSpec extends Specification {
OpenWeather ow = new OpenWeather()
def ‘default city and state return weather string'() {
when:
String result = ow.weather
println result
then:
result // not null is true in Groovy
result.contains(‘41.63’)
result.contains(‘-72.46’)
}
def "The weather is always great in Honolulu"() {
when:
String result = ow.getWeather(‘Honolulu’, ‘HI’)
println result
then:
result
result.contains(‘21.3’)
result.contains(‘-157.86’)
}
}
[/code]
Here’s a script to run the whole system:
[code language=”groovy”]
OpenWeather ow = new OpenWeather()
println ow.weather // called Marlborough, CT, but really Hartford
// Home of Paul King, co-author of _Groovy in Action_ and my personal hero
println ow.getWeather(‘Brisbane’,’Australia’)
// Home of Guillaume Laforge, head of the Groovy project
// (also one of my heroes, along with Dierk Koenig, Graeme Rocher, Tom Brady, David Ortiz, …)
println ow.getWeather(‘Paris’,’France’)
// Have to check the weather in Java, right?
println ow.getWeather(‘Java’,’Indonesia’)
// Any weather stations in Antarctica?
println ow.getWeather(”, ‘Antarctica’)
[/code]
Here are the results at the moment (Jan 7, 2014, at about 5:45pm EST):
Name : Marlborough Time : Tue Jan 07 17:43:10 EST 2014 Location : (41.63, -72.46) Weather : Clear (Sky is Clear) Icon : http://openweathermap.org/img/w/01n.png Current Temp : 8.312 F (high: 11.0 F, low: 5.0 F) Humidity : 35% Sunrise : Tue Jan 07 07:16:25 EST 2014 Sunset : Tue Jan 07 16:36:23 EST 2014 Wind : 3.4448818898 mph at 258 deg Cloudiness : 0% Name : Brisbane Time : Tue Jan 07 17:36:03 EST 2014 Location : (-27.47, 153.02) Weather : Clouds (broken clouds) Icon : http://openweathermap.org/img/w/04n.png Current Temp : 78.566 F (high: 82.0 F, low: 77.0 F) Humidity : 46% Sunrise : Mon Jan 06 14:00:36 EST 2014 Sunset : Tue Jan 07 03:47:48 EST 2014 Wind : 10.51360057266 mph at 121 deg Cloudiness : 80% Name : Paris Time : Tue Jan 07 17:39:23 EST 2014 Location : (48.85, 2.35) Weather : Clear (Sky is Clear) Icon : http://openweathermap.org/img/w/01n.png Current Temp : 52.682 F (high: 54.0 F, low: 51.0 F) Humidity : 81% Sunrise : Tue Jan 07 02:42:36 EST 2014 Sunset : Tue Jan 07 11:11:33 EST 2014 Wind : 8.052970651396 mph at 220 deg Cloudiness : 0% Name : Batununggal Time : Tue Jan 07 17:43:35 EST 2014 Location : (-6.96, 107.65) Weather : Clouds (scattered clouds) Icon : http://openweathermap.org/img/w/03n.png Current Temp : 68.5904 F (high: 69.0 F, low: 68.0 F) Humidity : 91% Sunrise : Mon Jan 06 17:40:32 EST 2014 Sunset : Tue Jan 07 06:10:58 EST 2014 Wind : 2.9303865426 mph at 200 deg Cloudiness : 44% Name : Time : Tue Jan 07 17:43:35 EST 2014 Location : (-78.33, 20.61) Weather : Clear (Sky is Clear) Icon : http://openweathermap.org/img/w/01d.png Current Temp : -20.2936 F (high: -20.0 F, low: -21.0 F) Humidity : 38% Sunrise : Tue Jan 07 16:43:35 EST 2014 Sunset : Wed Jan 08 04:43:35 EST 2014 Wind : 15.770400858984 mph at 15.0058 deg Cloudiness : 0%
So, yeah, it’s cold outside. I also freely admit it’s a little weird seeing those non-US temperatures converted to Fahrenheit.
The Gradle build file for this system is almost trivial:
[code language=”groovy”]
apply plugin:’groovy’
repositories {
mavenCentral()
}
dependencies {
compile ‘org.codehaus.groovy:groovy-all:2.2.1’
compile ‘com.google.code.gson:gson:2.2.4’
testCompile ‘org.spockframework:spock-core:0.7-groovy-2.0’
}
[/code]
All of this code can be found in the book’s Github repo. I added this to Chapter 2: Groovy by Example. Chapter 2 now uses a multi-project Gradle build, so it doesn’t look exactly that the one shown here, but it works the same way.
This process is easy enough to replicate for any RESTful service that returns JSON data. The time consuming part, if any, is writing the POGO structure to capture the JSON tree, but at least with POGOs the result is pretty simple.
————-
Now for a few personal notes. First, the latest Amazon review is simply beautiful. It’s from a person who knew Java and didn’t expect much from Groovy, but found out how much it could help. That’s exactly what I’m trying to achieve. In fact, I’ve been amazed that every single Amazon reviewer, no matter how much he or she did or didn’t like specific aspects of the book, definitely understood the goal and appreciated it.
Second, I have a few new projects in the works, but I think I can mention one of them. I’m currently generating a series of screencasts for the book, which I’m calling “Making Java Groovy: The Director’s Cut”. The goal is to discuss why the book was written as it was — what was included, what was left out, what has changed since its publication in the Fall of 2013, and anything else I think might be interesting or helpful. Everyone at Manning has been very supportive of the effort so far. I hope to make at least the first set of screencasts available soon.
Third, I’ll be giving a talk at the Boston Grails Users’ Group on Thursday, January 9, at 7pm. I hope to bring some books to add to the raffle, along with an admission to the NFJS event in the Boston area in March. If you’re in the area, please drop by and say hello.
Fourth, if you’ve read this far, you deserve a reward! If you use the coupon code “kousen37” at Manning.com, you can get 37% off any book at Manning. 🙂
Oh, and in case you didn’t hear me celebrating, I’m now officially a 2013 JavaOne Rock Star. Thank you very much to everyone who recommended me.
Finally, those of you battling the so-called “polar vortex”, stay warm! It’s cold here, but at least we’re still above 0 (F, not C). Next week I’m planning to brave the winds of Caradhras, otherwise known as central Minnesota. Brr.
As the saying goes, follow the great advice found on the side of a mayonnaise jar: “Keep cool but don’t freeze”.
Leave a Reply