JacksonとRetrofitで郵便番号APIを呼び出す

「プロになるJava」の補足的記事。

JSONはデータ交換で広く使われる形式で、機械にも人間にも読み書きしやすいということでよく使われています。 ということでJSONを扱うライブラリを使ってみましょう。

jp-postal-code-api

今回、扱うJSONとしては郵便番号から住所を得るWeb APIであるjp-postal-code-apiのデータを使います。
https://github.com/ttskch/jp-postal-code-api

Web APIはHTTPを介して呼び出すAPIで、JSONでやりとりすることが多いです。

ここでの住所データの形式はこのようになっています。

{
    "postalCode": "1000014",
    "addresses": [
        {
            "prefectureCode": "13",
            "ja": {
                "prefecture": "東京都",
                "address1": "千代田区",
                "address2": "永田町",
                "address3": "",
                "address4": ""
            },
            "kana": {
                "prefecture": "トウキョウト",
                "address1": "チヨダク",
                "address2": "ナガタチョウ",
                "address3": "",
                "address4": ""
            },
            "en": {
                "prefecture": "Tokyo",
                "address1": "Chiyoda-ku",
                "address2": "Nagatacho ",
                "address3": "",
                "address4": ""
            }
        }
    ]
}

Jackson

JSONを扱うときによく使われるライブラリがJacksonです。
https://github.com/FasterXML/jackson

説明を見るとDatabindというのがデータの結び付けができそう(bindは結び付けという意味です)。

dependencyの追加

ということでJackson Databindを見てみます。
https://github.com/FasterXML/jackson-databind

次のようにdependencyを書けばいいということが書いてあります。

今回はバージョンを直接かいて、次のようなタグをpom.xmlに追加します。

<dependencies>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.1</version>
  </dependency>
</dependencies>

Maven Dependenciesを見ると、jackson-databindだけではなくjackson-coreやjackson-annotasionsという、jackson-databindが依存したライブラリも読み込まれています。

テストデータの埋め込み

PostalCodeJSONSampleというクラスを作って、まずはJSONを文字列でもっておきます。

public class PostalCodeJSONSample {
    static String jsonFile = """
    {
        "postalCode": "1000014",
        "addresses": [
            {
                "prefectureCode": "13",
                "ja": {
                    "prefecture": "東京都",
                    "address1": "千代田区",
                    "address2": "永田町",
                    "address3": "",
                    "address4": ""
                },
                "kana": {
                    "prefecture": "トウキョウト",
                    "address1": "チヨダク",
                    "address2": "ナガタチョウ",
                    "address3": "",
                    "address4": ""
                },
                "en": {
                    "prefecture": "Tokyo",
                    "address1": "Chiyoda-ku",
                    "address2": "Nagatacho ",
                    "address3": "",
                    "address4": ""
                }
            }
        ]
    }
    """;
}

JSONに結びつけるrecordの作成

次のようなrecordでJSONに対応させます。

    record Area(String prefecture, String address1, String address2,
       String address3, String address4) {}
    record Address(String prefectureCode, 
       Area ja, Area kana, Area en) {}
    record PostalData(String postalCode, List<Address> addresses) {}

Listを使うので次のようなimportを追加

import java.util.List;

JacksonでのJSON読み込み

JacksonでJSONJavaクラスに対応させるには、まずObjectMapperのオブジェクトを作って、readValueメソッドにJSONデータとJavaクラスを渡して呼び出します。Javaクラスを渡すときはクラス名に.classをつけてクラスのオブジェクトを渡す必要があります。

    public static void main(String[] args) throws JsonProcessingException {
        var mapper = new ObjectMapper();
        PostalData data = mapper.readValue(jsonFile, PostalData.class);
        System.out.println(data);        
    }

JsonProcessingExceptionをthrowsにつけるので、importには次のような2つが必要です。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

そうすると、次のようにJSONファイルをPostalDataのオブジェクトとして扱えていることがわかります。

PostalData[postalCode=1000014, addresses=[Address[prefectureCode=13, 
ja=Area[prefecture=東京都, address1=千代田区, address2=永田町, address3=, address4=], 
kana=Area[prefecture=トウキョウト, address1=チヨダク, address2=ナガタチョウ, address3=, address4=], 
en=Area[prefecture=Tokyo, address1=Chiyoda-ku, address2=Nagatacho , address3=, address4=]]]]

コード全体は次のようになります。

package projava;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

public class PostalCodeJSONSample {
    record Area(String prefecture, String address1, String address2,
       String address3, String address4) {}
    record Address(String prefectureCode, 
       Area ja, Area kana, Area en) {}
    record PostalData(String postalCode, List<Address> addresses) {}

    public static void main(String[] args) throws JsonProcessingException {
        var mapper = new ObjectMapper();
        PostalData data = mapper.readValue(jsonFile, PostalData.class);
        System.out.println(data);        
    }
    
    static String jsonFile = """
    {
        "postalCode": "1000014",
        "addresses": [
            {
                "prefectureCode": "13",
                "ja": {
                    "prefecture": "東京都",
                    "address1": "千代田区",
                    "address2": "永田町",
                    "address3": "",
                    "address4": ""
                },
                "kana": {
                    "prefecture": "トウキョウト",
                    "address1": "チヨダク",
                    "address2": "ナガタチョウ",
                    "address3": "",
                    "address4": ""
                },
                "en": {
                    "prefecture": "Tokyo",
                    "address1": "Chiyoda-ku",
                    "address2": "Nagatacho ",
                    "address3": "",
                    "address4": ""
                }
            }
        ]
    }
    """;
}

Retrofit

次に、HTTPを介してWeb APIを呼び出してみましょう。HttpClientを使ってHTTPアクセスを行ってもいいのですが、ここではWeb APIJavaメソッドとして呼び出せるライブラリのRetrofit2を使います。
https://square.github.io/retrofit/

dependencyの追加

Downloadのところを見ると、Mavenのdependensyの書き方があるので、これを使います。

また、Jacksonを使ったコンバータを使うときにはconverter-jacksonを使うようなことが書いてありますね。

最新のバージョンはgithubを見にいくと2.11.0のようです。

ということでdependencyとしては次の2つを追加。

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.11.0</version>
</dependency>  
<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>converter-jackson</artifactId>
  <version>2.11.0</version>
</dependency>

APIに結びつけるinterfaceの作成

今回は次のようなAPIを呼び出します。

URLの一部がパラメータによって変わりますね。

次のようなJavaメソッドとして呼び出したいとしますね。

PostalData getAddress(String code)

そうすると、次のようなinterfaceを定義することになります。

    interface PostalAPI {
        @GET("api/v1/{postalCode}.json")
        Call<PostalData> getAddress(@Path("postalCode") String code);
    }

ここで、メソッドの戻り値はCallにして、ジェネリクスとして型を指定します。このとき、okhttp3にもCallがあるので、retrofit2のほうをimportするよう気を付けてください。

また、呼び出したいURLからドメイン名よりあとの部分を@GETアノテーションに指定します。パラメータの部分は中カッコで囲って適当な名前をつけておきます。ここではpostalCodeとします。

@GET("api/v1/{postalCode}.json")

引数に先ほどの名前を@Pathアノテーションで指定します。

@Path("postalCode") String code

今回はPostalCodeAPISampleというクラスにすべてを書くので、コードは次のようになります。

package projava;

import projava.PostalCodeJSONSample.PostalData;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;

public class PostalCodeAPISample {
    interface PostalAPI {
        @GET("api/v1/{postalCode}.json")
        Call<PostalData> getAddress(@Path("postalCode") String code);
    }
}

呼び出し

まずRetrofitのオブジェクトを作成します。このときBuilderを使って構築が必要で、次のようなコードになります。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://jp-postal-code-api.ttskch.com")
    .addConverterFactory(JacksonConverterFactory.create())
    .build();

Javaでは名前付き引数がないので、省略可能なたくさんの引数が必要になるときには、代わりにこのようなBuilderが使われることがあります。

Retrofitオブジェクトのcreateメソッドにインタフェースを渡すと、Web APIを呼び出せるオブジェクトが返ってきます。

PostalAPI api = retrofit.create(PostalAPI.class);

メソッドを呼び出すと、実行に必要なCallオブジェクトが返ってきて、execute()をすると実行されてbody()メソッドで結果が得れます。

PostalData data = api.getAddress("1600023").execute().body();
System.out.println(data);

次のように、Web APIの呼び出しが行えました。

PostalData[postalCode=1600023, addresses=[Address[prefectureCode=13, 
ja=Area[prefecture=東京都, address1=新宿区, address2=西新宿, address3=, address4=], 
kana=Area[prefecture=トウキョウト, address1=シンジュクク, address2=ニシシンジュク, address3=, address4=], 
en=Area[prefecture=Tokyo, address1=Shinjuku-ku, address2=Nishishinjuku , address3=, address4=]]]]

ただ、Retrofitは呼び出すとプロセスがしばらく終わらないので、気になる場合は終了させてください。

コード全体は次のようになります。

package projava;

import java.io.IOException;
import projava.PostalCodeJSONSample.PostalData;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;

public class PostalCodeAPISample {
    interface PostalAPI {
        @GET("api/v1/{postalCode}.json")
        Call<PostalData> getAddress(@Path("postalCode") String code);
    }
    
    public static void main(String[] args) throws IOException {
        var retrofit = new Retrofit.Builder()
            .baseUrl("https://jp-postal-code-api.ttskch.com")
            .addConverterFactory(JacksonConverterFactory.create())
            .build();
        var api = retrofit.create(PostalAPI.class);
        var data2 = api.getAddress("1600023").execute().body();
        System.out.println(data2);        
    }
}