Using Groovy to access and parse an exchange rate API

A groovy way to simplify bookkeeping: a step-by-step guide to converting currency exchange using Apache Groovy.
217 readers like this.
A dollar sign in a network

Opensource.com

I live in Canada and do a fair bit of work in other countries, so when I do my bookkeeping, I have to convert revenue and expenses in foreign currencies to Canadian dollars. I used to do this by laboriously looking up historical exchange rates at the Bank of Canada, but last year the bank reduced the number of currencies it supports. I decided to look for a different and more practical solution, using a website that publishes historical data on currency exchange rates and Apache Groovy, a programming language for the Java platform.

I found two foreign exchange history sites that permit some degree of free access in the form of calls to their APIs that return results in JSON, so I wrote a small script in Groovy to parse the JSON and give me back the info I need to enter in my bookkeeping program. Here's how I did it.

Exchange rate sites and their APIs

The first site I found is Fixer.io, which I really like. It is a free service that accesses data from the European Central Bank. The API is straightforward and simple to use. For example, if I want exchange rates for Canadian dollars and Indian rupees, I use the following URL:

https://api.fixer.io/latest?symbols=CAD,INR

which, when I ran that query for this article, returned the following JSON:

{
  "base":"EUR",
  "date":"2018-02-15",
  "rates":{"CAD":1.5604,"INR":79.849}
}

This shows that on Feb. 15, 2018, it took 1.5604 Canadian dollars to buy 1 euro and it took 79.849 Indian rupees to buy 1 euro. To figure out how many Indian rupees it took to buy 1 Canadian dollar, just divide 79.849 by 1.5604 to arrive at 51.172 Indian rupees per Canadian dollar. What could be easier?

Even better, Fixer.io is open source, published under the MIT License, and the code is available on GitHub.

Fixer.io addressed my need to convert Indian rupees to Canadian dollars, but unfortunately, it does not offer a solution for Chilean pesos, since the base data at the European Central Bank covers "only" 32 currencies.

To get information on Chilean pesos, I ended up at Currencylayer, which (at this time) provides data for 168 currencies. Currencylayer requires registration and charges a fee for its more valuable products and complex operations, but basic historical exchange rate conversion between U.S. dollars and 167 other currencies is free. Because of its broad coverage, I used Currencylayer to write this article.

Registering at Currencylayer gives the user a key that grants access to the tier of services selected. Assuming the key is K, then a URL like:

http://apilayer.net/api/historical?access_key=K&date=2018-01-01&currencies=CAD,EUR&format=1

will return the following JSON:

{
  "success":true,
  "terms":"https:\/\/currencylayer.com\/terms",
  "privacy":"https:\/\/currencylayer.com\/privacy",
  "historical":true,
  "date":"2018-01-01",
  "timestamp":1514851199,
  "source":"USD",
  "quotes":{
    "USDCAD":1.25551,
    "USDEUR":0.832296
  }
}

Using Groovy to access the API and parse the JSON results

Groovy provides some concise tools for dealing with URLs, streams of data, and JSON.

Starting at the JSON end, there is the JSON slurper and its handy parse() method. One form of the JSON slurper parse method takes a stream as its argument.

This is handy because, starting at the URL end, we can open a stream on a URL with its newInputStream() method.

Assuming we have built the URL string, that is, the API call, in a Groovy string variable called urlString, we can open the URL, read the stream produced, and parse the JSON with the following Groovy code:

def result = (new JsonSlurper()).parse(
	new InputStreamReader(
		(new URL(urlString)).newInputStream()
	)
)

The Groovy variable result is a map (that is, a set of key-value pairs) and looks like this:

[success:true, terms:https://currencylayer.com/terms, 
privacy:https://currencylayer.com/privacy, historical:true, 
date:2018-01-01, timestamp:1514851199, source:USD, quotes:
[USDCAD:1.25551, USDEUR:0.832296]]

This map corresponds precisely to the raw JSON shown above. For example, the key date has the value 2018-01-01.

Groovy allows us to access the value assigned to the date key either by:

result['date']

or

result.date

either of which will return the value 2018-01-01.

Note that the key quote refers itself to a map with two keys: one that gives the conversion between U.S. and Canadian dollars, the other that gives the conversion between U.S. dollars and the other currency of interest. In the above case, these can be accessed by

result['quotes']['USDCAD'] and
result['quotes']['USDEUR']

or

result.quotes.USDCAD and
result.quotes.USDEUR.

Using the latter format, and assuming the original amount is in a Groovy variable amtOrig, the amount in Canadian dollars can be calculated as:

def amtCAD = amtOrig / result.quotes.USDEUR * result.quotes.USDCAD

If I need to convert U.S. dollar expenses incurred while passing through DFW airport, for example, the formula is simpler:

def amtCAD = amtOrig * result.quotes.USDCAD

That's really all there is to it.

The (almost) working script

Let's turn it into a working Groovy script. We'll get the arguments from the command line and check them. Then if all looks good, we'll build the URL string, call the API, and get the result in map format. Finally, we'll pull apart the result map and calculate the exchange. Here's the script:

import groovy.json.JsonSlurper

// Check to make sure arguments are correct and print usage if not

if (args.size() != 3) {
    System.err.println "usage: groovy fx.groovy yyyy-mm-dd amount currency-code"
    System.exit(1)
}

// Check arguments for formatting and reasonable values

String dateString = args[0]
if (!(dateString ==~ /(19\d\d|2[01]\d\d)\-(0[1-9]|1[012])\-([012]\d|3[01])/)) { 1
    System.err.println "fx.groovy date $dateString not in format yyyy-mm-dd"
    System.exit(1)
}

String amountString = args[1]
if (!(amountString ==~ /\d+(\.\d+)?/)) { 1
    System.err.println "fx.groovy amount $amountString not numeric"
    System.exit(1)
}

String currencyCode = args[2]
if (!(currencyCode ==~ /[A-Z][A-Z][A-Z]/)) { 1
    System.err.println "fx.groovy currency-code $currencyCode not three capital letters, e.g. USD,CAD,EUR,GBP"
    System.exit(1)
}

// Our free license will only convert through USD so we adjust the currency list 
// according to whether the original currency was USD or something else

String currencyCodeList = currencyCode == 'USD' ? 'CAD' : "CAD,${currencyCode}" 2

// The access key code given during site signup

String accKey = 'a5bd4434d2299ecb3612ad297402481c' 3

// Build the URL string for the API call, get the JSON and parse it

String urlString = "http://apilayer.net/api/historical?access_key=$accKey&date=${dateString}&currencies=${currencyCodeList}&format=1" 2

def result = (new JsonSlurper()).parse(new InputStreamReader((new URL(urlString)).newInputStream()))

// Pick apart the values returned and compute the exchange info

BigDecimal amtOrig = new BigDecimal(amountString) 4
if (result.quotes.size() > 1) {
    String toUSDString = 'USD' + currencyCode
    BigDecimal amtCAD = amtOrig / result.quotes[toUSDString] * 5
        result.quotes.USDCAD 
    println "$amtOrig $currencyCode toUSD ${result.quotes[toUSDString]} toCAD ${result.quotes.USDCAD} $amtCAD" 2
} else {
    BigDecimal amtCAD = amtOrig * result.quotes.USDCAD 
    println "$amtOrig $currencyCode toCAD ${result.quotes.USDCAD} $amtCAD" 2
}

A few comments:

  1. Those are regular expression pattern matches. Yes, I could have used try... catch blocks.
  2. The notation ... ${foo}... is called a Gstring in Groovy; the thing in ${} is evaluated, and that value replaces it in the final string.
  3. That's not my access key! But it looks kind of like it.
  4. I'm using BigDecimal rather than double to carry out my calculations.
  5. I'm using [toUSDString] to access the value; this is because toUSDString is a variable, not a constant string.
  6. Long lines have wrapped around somewhat. Sorry about that!

If you haven't used Groovy much, some of the above will probably seem a bit magical, but it's not—really. I hope this encourages further study!

Note that people working with U.S. dollars as their base don't have to go through the intermediate computations.

And a final, cautionary comment: These and other historical exchange rates are indicative, generally computed from averages of other figures. So, if it cost you 2,623 Czech koruna to purchase 100 Euro on Feb 15, 2018, but Fixer.io gives you an exchange rate of 25.370 Czech koruna to the Euro on that date, it's not an error! You may have paid a different amount at a different financial institution.

Chris Hermansen portrait Temuco Chile
Seldom without a computer of some sort since graduating from the University of British Columbia in 1978, I have been a full-time Linux user since 2005, a full-time Solaris and SunOS user from 1986 through 2005, and UNIX System V user before that.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.