ラノベみたいなタイトルになっちゃいましたが許してください。
郵便番号から住所を自動入力させたい
そんな事よくありますよね。世の中には便利なAPIがたくさんありますが、有料だったりAPIの制限があったり、Formの形式にしか対応していなかったりで、実際のサービスに組み込む時に制限を受けてしまう事が多々あります。既に作ったformに良い感じに当てはめる方法を共有します。
前提
厳密なデータ管理は前提としません。もし厳密にデータを管理する必要があるのであれば、公開されている情報を取得し、自前でDBを用意して更新なども自分でやる必要があると思います。
郵便番号に対して紐づけられる住所は1:1ではないので、そこも考慮してのデータ設計が必要です。
今回想定している用途としては、ディレクターから「郵便番号を入力したらそれっぽい住所が補完される機能って欲しいよねー」って言われた時ぐらいのフランクなやつです。
使うもの
以上です。
使い方
とにかくサンプルコードをクレ!って人のためにフロントっぽい感じのものをこちらを置いておきます。
zip.js
const createTag = (src) => {
const script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
};
const requestZip = (preZip, sufZip) => {
const src = `https://yubinbango.github.io/yubinbango-data/data/${preZip}.js`;
return new Promise((resolve) => {
window['$yubin'] = callback.bind(this, `${preZip}${sufZip}`, resolve);
createTag(src);
});
};
const callback = (zipCode, resolve, res) => {
const zipData = res[zipCode];
let responseData = {
prefecturesId: '',
cityName: '',
address: '',
building: '',
};
if (zipData) {
responseData.prefecturesId = zipData.shift() || '';
responseData.cityName = zipData.shift() || '';
responseData.address = zipData.shift() || '';
responseData.building = zipData.shift() || '';
}
window['$yubin'] = () => {};
resolve(responseData);
};
export default requestZip;
index.html
<form>zip: <input id="input" type="text" /></form>
<pre id="target"></pre>
<script src="index.js"></script>
index.js
import requestZip from './zip.js';
document.getElementById('input').addEventListener('keyup', (ev) => {
const v = ev.target.value;
if (/^\d{7}$/.test(v)) {
const p = v.slice(0, 3);
const s = v.slice(-4);
requestZip(p, s).then((address) => {
document.getElementById('target').innerText = JSON.stringify(address);
});
}
});
何が起きているの?
今回使っているのは、formに良い感じのclassを付与すると良い感じに自動入力をしてくれるYubinBangoの中身を使っています。かつてajaxzip3にお世話になった人もいるかと思います。
YubinBangoの内部コードは公開されており、このあたりに書いてあるように、3つのリポジトリから成立しています。実際にライブラリで動いているCoreの部分はCoreのyubinbango-core.tsにほぼ書いてあります。
ここを見ると https://yubinbango.github.io/yubinbango-data/data
に対してjsonpリクエストを実行してjsファイルを呼び出し、中のデータを取得している様がよくわかると思います。データ実態はdataリポジトリにある状態です。
実際にdataのリポジトリを見てみると、dataフォルダの中に大量のjsファイルが入っているのを見る事が出来ます。
試しに適当に1ファイルを開いてみるとこんな構成になっています。
$yubin({
"0010000":[1,"札幌市北区",""],
"0010010":[1,"札幌市北区","北十条西"],
"0010011":[1,"札幌市北区","北十一条西"],
"0010012":[1,"札幌市北区","北十二条西"],
"0010013":[1,"札幌市北区","北十三条西"],
"0010014":[1,"札幌市北区","北十四条西"],
"0010015":[1,"札幌市北区","北十五条西"],
"0010016":[1,"札幌市北区","北十六条西"],
...
});
https://yubinbango.github.io/yubinbango-data/data/001.js より一部抜粋
fileの先頭に $yubin
があり、これがjsonpの実行関数になり、引数にObjectが入っている状態です。
つまり、該当のファイルをscriptタグで呼び出し、 $yubin
を実行すればObjectが引数に入ってくるので、欲しいデータを特定する事が出来ます。
サーバーサイドでは?
サーバー側でAPIをwrapしてサービスに利用したい人もいるでしょう。
その場合は先頭の $yubin
と末尾の閉じカッコを削除して、残ったjsonをObjectとして読み込んであげれば良い感じになります。 nodejsだと芸が無いのでPHPのサンプルコードを置いておきます。
<?php
function getZipObj($pre, $suf) {
$result = [
'prefecturesId' => null,
'cityName' => null,
'address' => null,
'building' => null
];
$data = file_get_contents("https://yubinbango.github.io/yubinbango-data/data/{$pre}.js");
// ここでゴリっと前後を切り落とす
$data = str_replace('$yubin(', '', str_replace(');', '', $data));
$zip = json_decode($data, true);
if($zip[$pre . $suf]) {
$zipData = $zip[$pre . $suf];
$result['prefecturesId'] = array_shift($zipData);
$result['cityName'] = array_shift($zipData);
$result['address'] = array_shift($zipData);
$result['building'] = array_shift($zipData);
}
return $result;
}
実際にはzip用の型とかを作った方がきれいな実装に出来るかなと思います。
まとめ
素敵なライブラリを提供してくれる開発者に感謝!!
個人的にはベストな解決法かなと思っているのですが、意外と知り合いのエンジニアにも浸透していなかったので記事にまとめました。
(あと個人的な忘備録)