この記事はsyumai Advent Calendar 2024の2日目の記事です。
月曜なので、本日は「これまで作ったツール・ライブラリ紹介」がテーマとなります。
本日のライブラリ
今回は、go-jpostcodeを紹介します。
go-jpostcodeとは?
go-jpostcodeは、郵便番号から住所を逆引きするのに使えるGoのライブラリです。
使い方
使い方は非常に単純で、 github.com/syumai/go-jpostcode
をimportして、下記のように関数呼び出しを行ったらOKです。
// 住所を1件検索する address, _ := jpostcode.Find("0010928") // 住所を複数件検索する (郵便番号には、複数の住所が紐付くことがあるので) addresses, _ := jpostcode.Search("1138654") // JSONとして出力する addressJSON, _ := address.ToJSON() fmt.Println(addressJSON)
応用例
例えば、このライブラリを使って郵便番号検索APIを簡単に立ち上げることができます。
下記の例では、 /${郵便番号}
で飛んできたリクエストにJSONでレスポンスを返しています。
$ go run github.com/syumai/go-jpostcode/_examples/server@v0.2.7 listening on http://localhost:8090 # 0010928 の住所を取得 $ curl -s localhost:8090/0010928 | jq . { "postCode": "0010928", "prefecture": "北海道", "prefectureKana": "ホッカイドウ", "prefectureCode": 1, "city": "札幌市北区", "cityKana": "サッポロシキタク", "town": "新川八条", "townKana": "シンカワ8ジョウ", "street": "", "officeName": "", "officeNameKana": "" }
コードは以下のリンクからご覧いただけます。
go-jpostcode/_examples/server/main.go at main · syumai/go-jpostcode · GitHub
データの取得元
郵便番号からの住所検索というと、日本郵便が配布する、かの有名なKEN_ALL.csvが情報源として存在します。 あらゆる会社ならびに組織、時によっては個人の手で、このデータをなんとかパースして、DBに格納したり、JSON化したりといった作業が行われていました。特に、ケンオールは非常に有名だと思います。 今回調べるまで知らなかったのですが、最近ではUTF-8化などの対応*1が行われていて、データの扱いやすさに改善がみられるようです。 とはいえ、あくまでCSVですし、これをこのままWebサービスに組み込むには少しどころではない、大変な手間があります。
ここで、非常にありがたいことに、なんとSmartHRさんが自社のOrganization配下でメンテナンスしてくださっている郵便番号データのJSONが存在します。(更新は手動で行われているのを感じます)
こちらを使ったjpostcode-rbというRubyのgemも存在しているようです。
go-jpostcodeでは、こちらのjpostcode-dataをgit submoduleとして追加した上で、そのデータをライブラリにバンドルする形で使わせていただいています。
実装上の工夫
jpostcode-dataは、郵便番号の先頭3桁をファイル名としたJSONファイルが1ディレクトリ配下に設置されている構造となっています。
例えば、001.json
, 002.json
, ... といった形式です。
001.json
のファイル冒頭は、例えばこのようになっていました。
{ "0000": { "postcode": "0010000", "prefecture": "北海道", "prefecture_kana": "ホッカイドウ", "prefecture_code": 1, "city": "札幌市北区", "city_kana": "サッポロシキタク", "town": "", "town_kana": "", "street": null, "office_name": null, "office_name_kana": null }, "0010": { "postcode": "0010010", "prefecture": "北海道", "prefecture_kana": "ホッカイドウ", ... }, ... }
例えば郵便番号検索で 9990145
が入力されたとき、 999.json
のファイル内のJSON Objectの 0145
というキーを探す必要があります。
こうした処理を行うとき、ディレクトリごとJSONを go:embed
して、検索するタイミングでパースするようでは遅いので、go-jpostcodeでは個々の郵便番号(9990145など)をキーとして事前にmapに展開した住所データをgobでdumpしています。
実は、元々は badger というインメモリのkey-value storeに、郵便番号ごとのJSONデータを格納し、badgerからキーで引いた後にJSONデコードする形式をとっていたのですが、najeiraさんから、全てmapに展開してしまった方がパフォーマンスがいいという指摘があり、なんとPRも出していただきました。
依存が減って、実装もシンプルになったのでとてもありがたかったです。確か自分は過去に、郵便番号全体に対するmapをコードとして生成する方法を試して失敗した記憶があり、najeiraさんの実装ではmapのリテラルを使わずに動的にキーに値を設定することでうまく回避できたようです。
ダンプしたgobのデータをgzip化したものがおよそ6MBで、これをそのままgo:embedで組み込んでいます。 go-jpostcode/map_adapter.go at fe6f6a09c95412c98543a644b9f19b329709d92f · syumai/go-jpostcode · GitHub
結局使わない形にはなってしまいましたが、badgerはライブラリ組み込みのkey-value storeとしては非常に扱いやすかったです。badgerもDBに読み込んだデータをdumpして、アプリケーションの起動時に簡単に内容を復元することができます。 今回のユースケースには合いませんでしたが、またどこかで使うことになるかもしれないとうっすら思っています。
CLIツールとしてのgo-jpostcode
go-jpostcodeでは、CLIツールとして「jpost」というコマンドの提供も行っています。
$ go install github.com/syumai/go-jpostcode/cmd/jpost@v0.2.7 # 郵便番号 "0010928" から住所を取得する $ jpost 0010928 {"postcode":"0010928","prefecture":"北海道",...
果たしてそんなことがあるのかわかりませんが、どうしてもターミナルから郵便番号検索したくなった時にご利用ください。
おまけ
こちらのライブラリ実装をネタとして、実は過去にもQiitaに記事を書いていました。
mitchellh作のmapstructureという素晴らしいライブラリの使いどころについて説明しているので、興味のある方はぜひこちらもご覧ください。 (って書いてから気付いたのですが、このライブラリ、Archiveされてるな……)