Ratpack入門 (5) - Json & Registry

## Ratpack入門シリーズ

1. [Ratpack入門 (1) - Ratpackとは](https://qiita.com/minebreaker/items/d7eeca815872a97069f5)
2. [Ratpack入門 (2) - アーキテクチャー](https://qiita.com/minebreaker/items/a9b256fd8a1513104669)
3. [Ratpack入門 (3) - hello world 詳解](https://qiita.com/minebreaker/items/b26d7d0677f91f17a7b5)
4. [Ratpack入門 (4) - ルーティング & 静的コンテンツ](https://qiita.com/minebreaker/items/8f26d8350f936ad669c2)
5. [Ratpack入門 (5) - Json & Registry](https://qiita.com/minebreaker/items/4353611cc541e3cb35fb)
6. [Ratpack入門 (6) - Promise](https://qiita.com/minebreaker/items/4689058d0d7b34d99c74)
7. [Ratpack入門 (7) - Guice & Spring](https://qiita.com/minebreaker/items/52b303bc18db5b33bd68)
8. [Ratpack入門 (8) - セッション](https://qiita.com/minebreaker/items/1ebaa492b257365e1e5e)
9. [Ratpack入門 (9) - Thymeleaf](https://qiita.com/minebreaker/items/50693a4d6614979f654c)


## Json

Ratpackは[Jackson](https://github.com/FasterXML/jackson)を使った組み込みのJSONサポート機能を持っています。JSONは標準で利用でき、`ratpack-jackson`モジュールを別途追加する必要はありません。

[公式](https://ratpack.io/manual/current/jackson.html)

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public static final class User {
    private int id;
    private String name;
}

public static void main( String[] args ) throws Exception {

    Action<Chain> handlers = chain -> {
        chain.all( ctx -> {
            ctx.byMethod( m -> {
                m.get( iCtx -> iCtx.render( Jackson.json( new User( 123, "John" ) ) ) );
                m.post( iCtx -> iCtx.parse( Jackson.fromJson( User.class ) ).then( user -> {
                    iCtx.render( "Received user: " + user );
                } ) );
                //　m.post( iCtx -> iCtx.parse( User.class ).then( user -> {
                //  　  iCtx.render( "Received user: " + user );
                //　} ) );
            } );
        } );
    };

    RatpackServer.start( server -> server
            .serverConfig( ServerConfig.builder().development( true ).build() )
            .handlers( handlers ) );
}
```

([lombok](https://projectlombok.org/)を使っています)

使い方は簡単で、`ratpack.jackson.Jackson`の各メソッドでラップしてあげるだけです。Jacksonを通して、オブジェクトをマーシャル/アンマーシャルしてくれます。`Jackson.fromJson()`は省略でき、コメントアウトされている部分で代替できます。これは、生の`Class`が渡されたとき用の`NoOptParserSupport`が自動的に追加されているためです。


## `Registry`と`Renderer`、`Parser`の関係

`render()`は`Object`を引数にとりますし、`parse()`は任意の`Class`を引数にとります。このとき、`Object`から適切なHTTPレスポンスへ変換される必要があります。[以前にも少し述べたように](https://qiita.com/minebreaker/items/b26d7d0677f91f17a7b5#handlers)、この変換には`Registry`に登録された`Renderer`クラス、`Parser`クラスが、その要求された方に応じて呼び出されることで、適切に変換されます。ですから、自分で設定したカスタムレンダラーをレジストリに登録することで、独自の変換を実現できます。

ここでは例として、CSVでデータを受け取ったり返却したりする仕組みを作ってみましょう。`User`は上記の例と同じです。

まず、`Renderer`を定義します。

```java
public static final class UserRenderer extends RendererSupport<User> {
    @Override
    public void render( Context ctx, User user ) throws Exception {
        ctx.getResponse().contentType( "text/plain" );
        ctx.render( user.getId() + "," + user.getName() );
    }
}
```

`Render`インターフェースのスケルトンである`RendererSupport`を継承します。ハンドラー内の`Context.render()`が`User`を引数として呼ばれると、`User`クラスを出力できる、このレンダラーが呼ばれます。引数はそのリクエストの`Context`と、`render()`に渡された`User`です。大体想像通りかと思います。レスポンスの出力方法は、ハンドラーでやっていた時と同様です。

```java
public static final class UserParser extends NoOptParserSupport {
    @Override
    protected <T> T parse( Context context, TypedData requestBody, TypeToken<T> type ) throws Exception {
        if ( type.getRawType().equals( User.class ) ) {
            String[] csv = requestBody.getText().split( "," );
            User user = new User( Integer.parseInt( csv[0] ), csv[1] );
            return Types.cast( user );
        } else {
            return null;
        }
    }
}
```

レンダラーと同様、パーサーも`Parser`インターフェースを実装する`NoOptParserSupport`スケルトンを継承します。`NoOptParserSupport`は、特に`parse(Class)`が呼ばれたときのための、最も簡単なパーサーを提供するために使います。`TypeToken`、すなわち要求されている方が`User`でなかった場合、このパーサーでは処理できないことを示すため`null`を返します。

```java
public static final class CustomHandler implements Handler {
    @Override
    public void handle( Context ctx ) throws Exception {
        ctx.byMethod( m -> {
            m.get( iCtx -> {
                iCtx.render( new User( 123, "John" ) );
            } );
            m.post( iCtx -> {
                iCtx.parse( User.class ).then( user -> {
                    iCtx.render( "Received: " + user );
                } );
            } );
        } );
    }
}
```

レジストリに登録する場合の動きを示すため、ここではハンドラーも独立したクラスにしてみました。

```java
public static void main( String[] args ) throws Exception {
    RatpackServer.start( server -> server
            .serverConfig( ServerConfig.builder().development( true ) )
            .registryOf( spec -> {
                spec.add( new UserRenderer() )
                    .add( new UserParser() )
                    .add( new CustomHandler() );
            } )
            .handler( CustomHandler.class ) );
}
```

`Registry`に、作成した独自クラスを登録してあげる必要があります。`handler()`メソッドに渡しているのがインスタンスではなく`Class`なのに注目してください。

#### 実行結果

```bash
$ curl localhost:5050 && echo
123,John
$ curl -X POST localhost:5050 --data "456,Jack" && echo
Received: Sample5_2_Registry.User(id=456, name=Jack)
```

CSVが独自定義のレンダラー、パーサーを通して処理されているのが確認できました。