JSONとオブジェクトの往復
このレッスンでは、JSON とオブジェクトの往復を Spring Boot がどう肩代わりしているのかを理解し、その変換処理の中身を自分の手で書けるようになります。
リクエストの本文はただの文字列です
クライアントから届くのは、次のような文字の並びでしかありません。
JSON
{"name":"taro","age":20}これは Java から見ればただの String です。{ や " の位置を読み取り、キーと値に切り分け、age は数値だと判断し、User クラスのフィールドへ入れる。この一連の作業を誰かがやらなければ、コントローラのメソッドはオブジェクトを受け取れません。
Spring Boot ではその作業を Jackson というライブラリが担当します。spring-boot-starter-web を入れると Jackson が一緒に入り、MappingJackson2HttpMessageConverter として自動で登録されます。だから開発者は変換コードを1行も書きません。
@RequestBody が入口です
Java
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public User create(@RequestBody UserRequest req) {
return new User(req.getName(), req.getAge());
}
}@RequestBody が付いた引数を見ると、Spring はリクエストの Content-Type を確認し、application/json であれば Jackson を選び、本文の文字列を UserRequest に変換してから引数へ渡します。この方向をデシリアライズと呼びます。
戻り値は逆向きに変換されます
@RestController が付いたクラスのメソッドの戻り値は、ビューの名前ではなくレスポンス本文そのものとして扱われます。返した User オブジェクトは Jackson によって JSON の文字列に組み立て直され、Content-Type に application/json が付いて返ります。この方向をシリアライズと呼びます。
| 向き | 呼び名 | きっかけ | 変換の中身 |
|---|---|---|---|
| JSON から Java | デシリアライズ | @RequestBody | 文字列を解析してフィールドへ詰める |
| Java から JSON | シリアライズ | 戻り値 | フィールドを読み出して文字列を組む |
何をキーにするのかを Jackson は知っています
Jackson は既定でアクセサ(getter と setter)の名前からプロパティ名を決めます。getName があれば name というキーになります。record の場合はアクセサ名がそのままフィールド名なので、name と age がそのままキーになります。名前を変えたいときは @JsonProperty("user_name") のようにフィールドやアクセサへ付けて指定します。
型が合わないとどうなるでしょうか
{"age":"twenty"} のように数値のはずの場所へ文字列が入っていると、変換の途中で失敗します。Spring Boot はこれを 400 Bad Request として扱います。つまり検証の前に、まず変換が通るかどうかという関門があるわけです。コントローラのメソッドには一度も入りません。
演習で書くもの
この演習では、Jackson がやっていることを極端に単純にした形で体験します。name=taro;age=20 のような区切り文字列を読み取り、キーと値に切り分け、値が数値だけでできているなら数値として、そうでなければ文字列としてクオートを付けて、JSON らしい文字列を組み立てます。Spring では @RequestBody を書けば自動で行われる処理を、ここでは自分の手で書きます。区切りの解析、型の判定、出力の組み立てという3つの仕事が変換の正体だと分かるはずです。
要件
- セミコロンで区切り、さらに最初のイコールでキーと値に分けること。キーと値の前後の空白は取り除くこと
- 値が数字だけで構成されていればクオートなしの数値として、そうでなければダブルクオートで囲んで出力すること
- 全体を波かっこで囲み、要素どうしはカンマでつなぐこと。入力が空または null のときは {} を返すこと
入出力例
toJson("name=taro;age=20") → "{"name":"taro","age":20}"
toJson("age=7") → "{"age":7}"
toJson("name=hanako;city=tokyo;age=35") → "{"name":"hanako","city":"tokyo","age":35}"
toJson("") → "{}"
toJson("name=a b;score=10x") → "{"name":"a b","score":"10x"}"