お天気情報をJSONでくれるWebAPIがあるので、それを利用してお天気アプリを作成しよう。

WebAPI確認

1.まずはWebAPIから吐き出されるJSONを確認しよう。
今回はlivedoorから提供されているWeather Hacks
のAPIを使用する。まずは東京のお天気情報のリクエストURLを叩いてレスポンスを確認しよう。
http://weather.livedoor.com/forecast/webservice/json/v1?city=130010

まるっとコピーしてJSON整形サイトで確認してもよいが、吐き出す内容が多いのでChromeにプラグインとしてインストールしたJSON formatterで確認してみる。

要素を折りたたむことができるので全体像をつかむのに便利だ。

2.アプリの仕様を決める。
すべての要素を盛り込んでアプリを作ってもいいが、今回は送信されるデータのうち、title,description,forecastsを利用することとする。

アプリ作成

下準備

1.エクリプス、新規動的Webプロジェクトから[joytas13]アプリを作成する。
2.GsonでJsonパースを行いたいので以下のファイルをWEB-INF/libの中に貼り付ける。

model

1.Jsonデータとにらめっこしながらmodelを作成する。今回は以下のような3つのクラスを作成した。
●model.Image.java(forecastがもっている画像情報クラス)

package model;

import java.io.Serializable;

public class Image implements Serializable{
	/*
	"width": 50,
	"url": "http://weather.livedoor.com/img/icon/15.gif",
	"title": "雨",
	"height": 31
	*/
	private int width;
	private String url;
	private String title;
	private int height;
	public Image(){}
	public int getWidth() {
		return width;
	}
	public void setWidth(int width) {
		this.width = width;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public int getHeight() {
		return height;
	}
	public void setHeight(int height) {
		this.height = height;
	}
	
	
}

●model.Forecast.java(1日分のお天気データクラス)
(Jsonにあるtemperatureは今回不使用)

package model;

import java.io.Serializable;
/*
 * {
"dateLabel": "今日",
"telop": "雨",
"date": "2017-09-17",
"temperature": {
"min": null,
"max": null
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/15.gif",
"title": "雨",
"height": 31
}
}
 */

public class Forecast implements Serializable{
	private String dateLabel;
	private String telop;
	private String date;
	private Image image;
	public Forecast(){}
	public String getDateLabel() {
		return dateLabel;
	}
	public void setDateLabel(String dateLabel) {
		this.dateLabel = dateLabel;
	}
	public String getTelop() {
		return telop;
	}
	public void setTelop(String telop) {
		this.telop = telop;
	}
	public String getDate() {
		return date;
	}
	public void setDate(String date) {
		this.date = date;
	}
	public Image getImage() {
		return image;
	}
	public void setImage(Image image) {
		this.image = image;
	}
	
}

●model.Weather.java(本体となるクラス、3日分の天気などをhas-aで持つ)
(使いたい部分を抽出してクラスのフィールドとする)

package model;

import java.io.Serializable;
/*
 * "pinpointLocations": [],
"link": "http://weather.livedoor.com/area/forecast/130010",
"forecasts": [],
"location": {},
"publicTime": "2017-09-17T17:00:00+0900",
"copyright": {},
"title": "東京都 東京 の天気",
"description": {
"text": " 前線が日本の南に停滞しています。また、大型の台風第18号が四国の南\n西海上にあって、北東へ進んでいます。\n\n【関東甲信地方】\n 関東甲信地方は、おおむね雨となっています。\n\n 17日は、前線や台風の影響によりおおむね雨となり、雷を伴い非常に激\nしく降る所がある見込みです。\n\n 18日は、前線や台風の影響により、はじめは雨で雷を伴い非常に激しく\n降る所がありますが、次第に晴れるでしょう。\n\n 関東近海では、18日にかけて、うねりを伴い大しけとなる見込みです。\n船舶は高波に警戒してください。\n\n【東京地方】\n 17日は、雨で夜は雷を伴い激しく降る所があるでしょう。\n 18日は、曇り後晴れで、明け方まで雨で雷を伴い激しく降る所がある見\n込みです。",
"publicTime": "2017-09-17T16:55:00+0900"
}
 */

public class Weather implements Serializable{
	private String title;
	private String description;
	private Forecast[] forecasts;
	public Weather(){}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public Forecast[] getForecasts() {
		return forecasts;
	}
	public void setForecasts(Forecast[] forecasts) {
		this.forecasts = forecasts;
	}
	
}

controller

1.リクエストを処理するコントローラーを以下のように作成する。
[処理の流れ]
WebAPIにHttpリクエストを投げて、その結果(Json)をInputStreamで取得。
取得したデータをGsonでパースしてWeatherオブジェクトを生成し、リクエストスコープに詰めている。

なお、Jsonに含まれる改行コードの部分をbrタグに変換するメソッドを別メソッドにしている。
(こうすることで改行コード部分をHtml上で改行させることができる)
●controller.Main.java(Servlet)

package controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;

import model.Forecast;
import model.Image;
import model.Weather;

/**
 * Servlet implementation class Main
 */
@WebServlet("/Main")
public class Main extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//webAPIのurlからURLインスタンスを作成
		URL url=new URL("http://weather.livedoor.com/forecast/webservice/json/v1?city=130010");
		//GETでの通信処理
		HttpURLConnection con=(HttpURLConnection)url.openConnection();
		con.setRequestMethod("GET");
		//InputStreamで結果を取得
		InputStream is=con.getInputStream();
		//スプーンからスコップ
		InputStreamReader isr=new InputStreamReader(is,"UTF-8");
		//ファイル読み込み時にはBufferedReaderだがここではJsonReaderインスタンスを取得する。
		JsonReader reader=new JsonReader(isr);
		//ルートが{}なのでオブジェクトとして取得
		JsonObject root=new Gson().fromJson(reader,JsonObject.class);
		//結果として必要となるWeatherインスタンスをnew(フィールドの値はすべてnull)
		Weather w=new Weather();
		//Jsonからプロパティがtitleの項目を探してきてそれをStringに変換してwにセットする。
		w.setTitle(root.get("title").getAsString());
		//descriptionはオブジェクトを値として持っているのでまずはそれを取得し、その中にあるtextを取得(メソッドチェーン)
		//結果の文字列に改行文字が含まれているのでそれを<br>という文字列に変換しておく。(メソッドは下部にある)
		w.setDescription(nl2br(root.get("description").getAsJsonObject().get("text").getAsString()));
		//forecastsは配列なので配列として取得
		JsonArray fArray=root.get("forecasts").getAsJsonArray();
		//Forecast型のインスタンスを格納する配列を準備
		Forecast[] forecasts=new Forecast[fArray.size()];
		//配列とforは刺身と醤油の相性。JsonArrayの要素数はsize()で求められる
		for(int i=0;i<fArray.size();i++) {
			//配列の要素一つ一つはオブジェクトなのでそれを取得
			JsonObject fObj=fArray.get(i).getAsJsonObject();
			//取得した情報をもとにインスタンスを生成したいのでまずはフィールドがnullの状態でnew
			Forecast f=new Forecast();
			//JSONから情報を取得し、fにセットしていく
			f.setTelop(fObj.get("telop").getAsString());
			f.setDateLabel(fObj.get("dateLabel").getAsString());
			f.setDate(fObj.get("date").getAsString());
			//パラメータimageはオブジェクトなのでJsonオブジェクトとして取得
			JsonObject iObj=fObj.get("image").getAsJsonObject();
			//その情報をもとにインスタンスを作りたいのでまずはnew
			Image image=new Image();
			//情報をもとにimageに詰める
			image.setHeight(iObj.get("height").getAsInt());
			image.setTitle(iObj.get("title").getAsString());
			image.setUrl(iObj.get("url").getAsString());
			image.setWidth(iObj.get("width").getAsInt());
			//image要素ができたのでfにセットする
			f.setImage(image);
			//fが一つできたので配列にセットする
			forecasts[i]=f;
		}
		//for文が回り終わるとすべてのforecastインスタンスが詰まっているのでおおもとのwにセット
		w.setForecasts(forecasts);
		//完成したw(Weater)インスタンスをリクエストスコープに詰める
		request.setAttribute("weather", w);
		//フォワード処理
		RequestDispatcher rd=request.getRequestDispatcher("/WEB-INF/view/main.jsp");
		rd.forward(request,response);

	}

	//文字列に含まれる改行コードを<br>タグに置き換えるメソッド
		public static String nl2br(String str) {
		    if (str == null || str.equals("")) {
		      return "";
		    }
		    str = str.replace("\n", "<br>");
		    return str;
		  }
}

view

1.modelとcontrollerの連携により欲しいデータが作れたので後はviewで表示するだけだ。
いつものように/WEB-INF/の中にviewフォルダを作ってmain.jspを配置しよう。
●/WEB-INF/view/main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.*"%>
<%
Weather w=(Weather)request.getAttribute("weather");

%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title><%=w.getTitle() %></title>
</head>
<body>
<h1><%=w.getTitle() %></h1>
<p><%=w.getDescription() %></p>
<table border="1">
<%for(Forecast f:w.getForecasts()) {%>
<tr>
<td><%=f.getDateLabel() %></td>
<td><%=f.getTelop() %></td>
<td><%=f.getDate() %></td>
<%Image image=f.getImage(); %>
<td><img src="<%=image.getUrl()%>" width="<%=image.getWidth() %>" height="<%=image.getHeight() %>" alt="<%=image.getTitle()%>"></td>
</tr>
<%} %>
</table>
</body>
</html>

完成品

1.以下のように東京の3日分(時間帯によっては2日分)のお天気情報が表示されれば成功だ。