使用 Groovy 访问和解析汇率 API

简化记账的 Groovy 方法:使用 Apache Groovy 转换货币汇率的分步指南。
217 位读者喜欢这篇文章。
A dollar sign in a network

Opensource.com

我住在加拿大,并且在其他国家做很多工作,所以当我记账时,我必须将外币的收入和支出转换为加元。我过去常常在加拿大银行费力地查找历史汇率,但去年该银行减少了其支持的货币数量。我决定寻找一种不同且更实用的解决方案,使用一个发布货币汇率历史数据的网站和 Apache Groovy,这是一种用于 Java 平台的编程语言。

我找到了两个外汇历史网站,允许以调用其 API 的形式进行一定程度的免费访问,API 以 JSON 格式返回结果,因此我用 Groovy 编写了一个小脚本来解析 JSON 并返回我需要在我的记账程序中输入的信息。以下是我如何做的。

汇率网站及其 API

我找到的第一个网站是 Fixer.io,我非常喜欢它。它是一项免费服务,可以访问来自 欧洲中央银行 的数据。该 API 直观且易于使用。例如,如果我想要加元和印度卢比的汇率,我使用以下 URL

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

当我为本文运行该查询时,它返回了以下 JSON

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

这表明在 2018 年 2 月 15 日,购买 1 欧元需要 1.5604 加元,购买 1 欧元需要 79.849 印度卢比。要计算出购买 1 加元需要多少印度卢比,只需将 79.849 除以 1.5604,即可得出每加元 51.172 印度卢比。还有什么比这更容易的呢?

更棒的是,Fixer.io 是开源的,根据 MIT 许可证发布,代码在 GitHub 上可用

Fixer.io 解决了我的将印度卢比转换为加元的需求,但不幸的是,它没有为智利比索提供解决方案,因为欧洲中央银行的基础数据“仅”涵盖 32 种货币。

为了获得关于智利比索的信息,我最终找到了 Currencylayer,它(目前)提供 168 种货币的数据。Currencylayer 需要注册,并对其更有价值的产品和复杂操作收费,但美元与 167 种其他货币之间的基本历史汇率转换是免费的。由于其广泛的覆盖范围,我使用 Currencylayer 撰写了本文。

在 Currencylayer 注册后,用户将获得一个密钥,该密钥授予对所选服务层级的访问权限。假设密钥为 K,则类似如下的 URL

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

将返回以下 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
  }
}

使用 Groovy 访问 API 并解析 JSON 结果

Groovy 提供了一些简洁的工具来处理 URL、数据流和 JSON。

从 JSON 端开始,有 JSON slurper 及其方便的 parse() 方法。JSON slurper 的一种形式的 parse 方法接受流 作为其参数。

这很方便,因为从 URL 端开始,我们可以使用其 newInputStream() 方法 在 URL 上打开一个流

假设我们已经在名为 urlString 的 Groovy 字符串变量中构建了 URL 字符串,即 API 调用,我们可以打开 URL,读取生成的流,并使用以下 Groovy 代码解析 JSON

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

Groovy 变量 result 是一个映射(即,一组键值对),看起来像这样

[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]]

此映射与上面显示的原始 JSON 完全对应。例如,键 date 的值为 2018-01-01

Groovy 允许我们通过以下方式访问分配给 date 键的值

result['date']

result.date

这两者都将返回值 2018-01-01

请注意,键 quote 本身指的是一个映射,其中包含两个键:一个给出美元和加元之间的转换,另一个给出美元和另一种感兴趣的货币之间的转换。在上述情况下,可以通过以下方式访问这些键

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

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

使用后一种格式,并假设原始金额在 Groovy 变量 amtOrig 中,则可以按以下公式计算加元金额

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

例如,如果我需要转换通过 DFW 机场时发生的美元费用,则公式更简单

def amtCAD = amtOrig * result.quotes.USDCAD

这就是全部内容。

(几乎)可用的脚本

让我们将其转换为可用的 Groovy 脚本。我们将从命令行获取参数并检查它们。然后,如果一切看起来都很好,我们将构建 URL 字符串,调用 API,并以映射格式获取结果。最后,我们将分解结果映射并计算汇率。这是脚本

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
}

一些评论

  1. 这些是正则表达式模式匹配。是的,我可以使用 try... catch 块。
  2. 符号 ... ${foo}... 在 Groovy 中称为 Gstring;${} 中的内容会被评估,并且该值会替换最终字符串中的内容。
  3. 那不是我的访问密钥!但它看起来有点像。
  4. 我正在使用 BigDecimal 而不是 double 来进行计算。
  5. 我正在使用 [toUSDString] 来访问该值;这是因为 toUSDString 是一个变量,而不是一个常量字符串。
  6. 长行在某种程度上换行了。对此感到抱歉!

如果您不太了解 Groovy,那么以上一些内容可能看起来有点神奇,但实际上并非如此。我希望这能鼓励您进一步学习!

请注意,以美元为基础货币的人们不必进行中间计算。

最后一个警示性评论:这些和其他历史汇率是指示性的,通常从其他数据的平均值计算得出。因此,如果在 2018 年 2 月 15 日购买 100 欧元花费了您 2,623 捷克克朗,但 Fixer.io 给出的当日欧元兑换率为 25.370 捷克克朗,这不是错误!您可能在不同的金融机构支付了不同的金额。

Chris Hermansen portrait Temuco Chile
自从 1978 年从不列颠哥伦比亚大学毕业以来,我几乎离不开计算机,自 2005 年以来一直是全职 Linux 用户,从 1986 年到 2005 年是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.