回應與接收 JSON


現在前端工程當道,為了實現前後端技術分離,Web 應用程式可以提供中性的資料格式,與前端進行溝通,其中最受歡迎的格式之一就是 JSON

Spring MVC 可以自動剖析物件傳回 JSON,為此,你需要有個轉換器,對 Spring MVC 來說,只需要在類別路徑中找得到轉換器的類別就可以了,你可以在 build.gradle 中加入:

compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7'

直接來看看簡單的範例,就可以知道怎麼回應 JSON:

package cc.openhome.controller;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import cc.openhome.model.Account;

@Controller
@RequestMapping("api")
public class Api {
    @GetMapping("test")
    @ResponseBody
    public Map<String, Object> test() {
        Map<String, Object> data = new HashMap<>();
        data.put("a", "10");
        data.put("b", 20);
        data.put("c", new String[] {"foo", "orz"});
        data.put("d", new Date());
        return data;
    }

    @GetMapping("test2")
    @ResponseBody
    public Account test2() {
        return new Account("1", "2", "3", "4");
    }

    @PostMapping("test3")
    @ResponseBody
    public Account test3(@RequestBody Account account) {
        return new Account(
            account.getName().toUpperCase(), 
            account.getEmail().toUpperCase(), 
            account.getPassword(),
            account.getSalt()
        );
    }
}

若請求 /api/test,會自動加上 Content-Type: application/json;charset=UTF-8 回應標頭,而傳回的 JSON 會是:

{"a":"10","b":20,"c":["foo","orz"],"d":1543820089201}

若請求 /api/test2,傳回的 JSON 會是:

{"name":"1","email":"2","password":"3","salt":"4"}

如果要能接收 JSON,並轉換為指定的型態的話,該型態必須有無參建構式,而要設定的屬性必須有對應的 Setter,然後,只要在處理器參數上標示 @RequestBody 就可以了。

例如,可以將底下的片段,置於某個 HTML 之中,使用瀏覽器請求網站上的 HTML,就可以看到轉換為大寫後傳回的資料:

<div id="console"></div>
<script>
fetch('api/test3', {
    method : 'POST',
    headers : {
        'Content-Type' : 'application/json;charset=UTF-8'
    },
    body : JSON.stringify({
        name  : 'caterpillar',
        email : 'caterpillar@openhome.cc',
        password : '12345678',
        salt : '123352453253'
    })
})
.then(resp => resp.json())
.then(acct =>  document.getElementById('console').innerHTML = `${acct.name}, ${acct.email}`);
</script>

如果實際上,控制器中的處理器都會標註 @ResponseBody,那麼可以改在控制器上標註 @RestController,省去個別標註 @ResponseBody 的麻煩,例如:

package cc.openhome.controller;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cc.openhome.model.Account;

@RestController
@RequestMapping("api")
public class Api {
    @GetMapping("test")
    public Map<String, Object> test() {
        Map<String, Object> data = new HashMap<>();
        data.put("a", "10");
        data.put("b", 20);
        data.put("c", new String[] {"foo", "orz"});
        data.put("d", new Date());
        return data;
    }

    @GetMapping("test2")
    public Account test2() {
        return new Account("1", "2", "3", "4");
    }

    @PostMapping("test3")
    public Account test3(@RequestBody Account account) {
        return new Account(
            account.getName().toUpperCase(), 
            account.getEmail().toUpperCase(), 
            account.getPassword(),
            account.getSalt()
        );
    }
}