レスポンス(JSON)のJavaオブジェクト変換


【最終更新日】2023-04-16

FlaskアプリのJSONレスポンスをJavaオブジェクトに変換する方法について解説します。

Javaデータクラスのフィールド名がJSONのプロパティ名に一致ししていればGsonライブラリがJSON文字列を対応するJavaオブジェクトに変換してくれます。

【レスポンス(JSON)とJavaクラスのマッピング】
1.開発・テスト用のプロジェクト
(1)このページの説明で使われているJavaプロジェクトのクラス構成
java-health-care-example/
├── com
│   └── examples
│       └── android
│           └── healthcare
│               ├── Constants.java                  (*) 入出力先定義
│               ├── ParseResponseDataJson.java      (*) データ取得時レスポンス(JSON)Java変換メインクラス
│               ├── ParseResponseWarningJson.java   (*) ウォーニングレスポンス(JSON)Java変換メインクラス
│               ├── data
│               │   ├── GetCurrentDataResult.java   (*) データ取得時レスポンス結果クラス 
│               │   └── ResponseWarningStatus.java  (*) ウォーニングレスポンスクラス
│               └── util
│                   └── FileUtil.java            (*) ファイル入出力クラス
└── lib
    └── gson-2.9.0.jar    (*) Mavenリポジトリからダウンロードしlibディレクトリに配置
Javaプロジェクトのソースコードはこちら
https://github.com/pipito-yukio/personal_healthcare/tree/main/src/java-health-care-example
(2)このページの説明で使われているFlaskプロジェクトのpythonファイル
Healthcare/
└── healthcare
    ├── __init__.py
    └── views
        ├── __init__.py
        └── app_main.py (*) アプリメイン (リクエスト処理)
Flaskアプリのソースコードはこちら
https://github.com/pipito-yukio/personal_healthcare/tree/main/src/webapp
2.レスポンス用JSON文字列をJavaオブジェクトに変換
(1).A データ取得時レスポンス結果クラス ※toString()メソッドは省略
package com.examples.android.healthcare.data;

public class GetCurrentDataResult {
    private final RegisterData data;
    private final ResponseStatus status;

    public GetCurrentDataResult(RegisterData data, ResponseStatus status) {
        this.data = data;
        this.status = status;
    }

    public RegisterData getData() { // JSONのプロパティ名"data"で登録データオブジェクトを格納
        return data;
    }

    public ResponseStatus getStatus() { // JSONのプロパティ名"status"でステータスオブジェクトを格納
        return status;
    }
    //...省略...
}
(1).B ウォーニングステータスクラス ※toString()メソッドは省略
package com.examples.android.healthcare.data;

public class ResponseWarningStatus {
    private final ResponseStatus status;

    public ResponseWarningStatus(ResponseStatus status) {
        this.status = status;
    }
    public ResponseStatus getStatus() { // JSONのプロパティ名"status"でステータスオブジェクトを格納
        return status;
    }
    //...省略...
}
(2) 登録データ取得用データ: response_getcurrentdata.json ※curlで取得した結果をファイル保存
{
  "data": {
    "emailAddress": "user1@examples.com", 
    "healthcareData": {
      "bloodPressure": {
        "eveningMax": 132, 
        "eveningMeasurementTime": "22:55", 
        "eveningMin": 82, 
        "eveningPulseRate": 61, 
        "morningMax": 119, 
        "morningMeasurementTime": "06:40", 
        "morningMin": 70, 
        "morningPulseRate": 69
      }, 
      "bodyTemperature": {
        "measurementTime": null, 
        "temperature": null
      }, 
      "nocturiaFactors": {
        "conditionMemo": "ちょっと風邪気味", 
        "hasAlcohol": false, 
        "hasCoffee": true, 
        "hasDiuretic": false, 
        "hasNutritionDrink": false, 
        "hasSportsDrink": false, 
        "hasTea": false, 
        "midnightToiletVisits": 0, 
        "takeBathing": false, 
        "takeMedicine": false
      }, 
      "sleepManagement": {
        "deepSleepingTime": null, 
        "sleepScore": 83, 
        "sleepingTime": "06:40", 
        "wakeupTime": "05:30"
      }, 
      "walkingCount": {
        "counts": 7870
      }
    }, 
    "measurementDay": "2023-03-01", 
    "weatherData": {
      "weatherCondition": {
        "condition": "曇り"
      }
    }
  }, 
  "status": {
    "code": 0, 
    "message": "OK"
  }
}
(3).A メインクラスでデータ取得結果をJavaオブジェクトに変換
package com.examples.android.healthcare;

import com.examples.android.healthcare.data.GetCurrentDataResult;
import com.examples.android.healthcare.util.FileUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.FileNotFoundException;
import java.nio.file.Paths;
import java.util.List;

/**
 * 健康管理Flaskアプリからのデータ取得リクエストのレスポンス(JSON)をJava Objectに変換する
 * curlで取得したレスポンスを入力ファイルとする
 * $ curl -G -d 'emailAddress=user1@examples.com&measurementDay=2023-03-10' 
 *                             http://dell-t7500.local:5000/healthcare/getcurrentdata
 */
public class ParseResponseDataJson {
    static final String JSON_NAME = "response_getcurrentdata.json";

    public static void main(String[] args) {
        // 入力ファイル名
        // ~/Documents/java/input/response_getcurrentdata.json
        String readFile = Paths.get(Constants.INPUT_DATA_PATH, JSON_NAME).toString();

        try {
            // テスト用ファイルからJSON文字列を取得する
            List<String> responseLines = FileUtil.readLines(readFile);
            String responseJson = String.join("", responseLines);
            Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
            // Androidアプリで使用するデータ取得レスボンスデータクラス
            GetCurrentDataResult respObj = gson.fromJson(responseJson, GetCurrentDataResult.class);
            System.out.println(respObj);
        } catch (FileNotFoundException e) {
            System.out.println(e.getLocalizedMessage());
        } catch (Exception e) {
            // パースエラー
            System.out.println(e.getLocalizedMessage());
        }
    }
}
3.B Javaオブジェクトのコンソール出力 ※整形しています(実際は1行です)
GetCurrentDataResult{
  data=RegisterData{
       emailAddress='user1@examples.com', measurementDay='2023-03-01', 
       healthcareData=HealthcareData{
         sleepManagement=SleepManagement{
           wakeupTime='05:30', sleepScore=83, sleepingTime='06:40', deepSleepingTime='null'
         }, 
         bloodPressure=BloodPressure{
           morningMeasurementTime=06:40, morningMax=119, morningMin=70, morningPulseRate=69, 
           eveningMeasurementTime=22:55, eveningMax=132, eveningMin=82, eveningPulseRate=61
         },
         bodyTemperature=BodyTemperature{
           measurementTime='null', temperature=null
         },
         nocturiaFactors=NocturiaFactors{
           midnightToiletVisits=0, hasCoffee=true, hasTea=false, hasAlcohol=false, 
           hasNutritionDrink=false, hasSportsDrink=false, hasDiuretic=false, takeMedicine=false, 
           takeBathing=false, conditionMemo='ちょっと風邪気味'
         }, 
         walkingCount=WalkingCount{
           counts=7870
         }
       },
       weatherData=WeatherData{
         weatherCondition=weatherCondition{
           condition='曇り'}
         }
       },
  status=ResponseStatus{
    code=0, message='OK'
  }
}
(4).A メインクラスでウォーニング文字列をJavaオブジェクトに変換
package com.examples.android.healthcare;

import com.examples.android.healthcare.data.ResponseWarningStatus;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * 健康管理FlaskアプリからのWarningレスポンスをJava Objectに変換する
 * curlで取得したレスポンスを入力文字列とする ※未登録の日付を指定
 * $ curl -G -d 'emailAddress=user1@examples.com&measurementDay=2023-04-10' 
 *                             http://dell-t7500.local:5000/healthcare/getcurrentdata
 * {
 *   "status": {
 *   "code": 404,
 *   "message": "Data is not found."
 *   }
 * }
 */
public class ParseResponseWarningJson {
    // データ取得リクエストでレコード未登録時に返却されるレスポンス
    static final String jsonText = "{\"status\": {\"code\": 404, \"message\": \"Data is not found.\" }}";

    public static void main(String[] args) {
        Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
        try {
            ResponseWarningStatus respObj = gson.fromJson(jsonText, ResponseWarningStatus.class);
            System.out.println(respObj);
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
        }
    }
}
(4).B 出力結果
ResponseWarningStatus{status=ResponseStatus{code=404, message='Data is not found.'}}
【参考】Androidアプリのデータ取得メソッド ■■ 説明用にコメント追加しています
※ Androidアプリのコード量は数千ステップにもなるのでJavaプロジェクトで動作確認したほうがずっと簡単です。
/**
 * GETリクエストで該当するデータを取得する
 * @param emailAddress メールアドレス
 * @param pastDay 過去の測定日付
 */
private void sendGetCurrentDataRequest(String emailAddress, String pastDay) {
    RequestDevice device =  NetworkUtil.getActiveNetworkDevice(getContext());
    if (device == RequestDevice.NONE) {
        showDialogNetworkUnavailable();
        return;
    }

    showActionBarGetting(device);
    HealthcareApplication app = (HealthcareApplication) requireActivity().getApplication();
    String requestUrl = app.getmRequestUrls().get(device.toString());
    Map headers = app.getRequestHeaders();
    // GETリクエスト送信: 登録済みデータの取得
    HealthcareRepository repository = new GetCurrentDataRepository();
    String requestUrlWithPath = requestUrl + repository.getRequestPath(0);
    // リクエストパラメータ: 主キー項目(メールアドレス, 測定日付)
    String requestParams = AppTopUtil.getRequestParams(emailAddress, pastDay);
    repository.makeGetRequest(0, requestUrl, requestParams, headers,
            app.mEexecutor, app.mHandler, (result) -> {
                // リクエストURLをAppBarに表示
                showActionBarResult(requestUrlWithPath);

                // 送信ボタンを戻す
                mBtnSend.setEnabled(true);
                if (result instanceof Result.Success) {
                    // ■■ 1.(1) 正常レスポンス(JSON)をGetCurrentDataResultオブジェクトとして取得
                    GetCurrentDataResult dataResult =
                            ((Result.Success) result).get();
                    // ■■ 1.(2) プロパティ["data"]に格納されていた登録データを取得
                    RegisterData data = dataResult.getData();
                    // 古いオブジェクトを破棄する
                    if (mRegisterDataForUpdate != null) {
                        mRegisterDataForUpdate = null;
                    }
                    // https://www.baeldung.com/java-deep-copy
                    //  6.3. JSON Serialization With Jackson
                    Gson gson = new Gson();
                    // 各入力フィールドに変更があったかどうかを確認するためのオブジェクト
                    mRegisterDataForUpdate =
                            gson.fromJson(gson.toJson(data), RegisterData.class);
                    // ウィジット更新
                    updateInputWidgetsFromRegisterData(data); // ■■ 1.(3) 登録データを該当するウィジットに設定する
                    // ステータス更新
                    showStatus(getString(R.string.message_get_current_ok));
                    // 更新用モニター開始
                    startUpdateMonitor();
                    // 送信ボタンのラベルを"更新"に変更
                    changePostRequestWithButton(PostRequest.UPDATE);
                } else if (result instanceof Result.Warning) {
                    // ■■ 2.(1) ウォーニングレスポンス(JSON)をResponseStatusオブジェクトとして取得
                    ResponseStatus status =
                        ((Result.Warning) result).getResponseStatus();
                    DEBUG_OUT.accept(TAG, "WarningStatus: " + status);
                    if (status.getCode() == 404) {
                        // 未登録(404)なら新規登録モードにリセット
                        mBtnSave.setEnabled(true);
                        resetToNewRegistrationMode();
                    }
                    showWarning(getResponseWarning(status)); // ■■  2.(2) ウォーニングをステータスに表示 
                } else {
                    // 例外メッセージをダイアログに表示
                    Exception exception = ((Result.Error) result).getException();
                    Log.w(TAG, "GET error:" + exception.toString());
                    String errorMessage = String.format(
                            getString(R.string.exception_with_reason),
                            exception.getLocalizedMessage());
                    showMessageDialog(getString(R.string.error_response_dialog_title),
                            errorMessage,"ExceptionFragment");
                }
            });
}
4.FlaskアプリのJSONレスポンス返却処理

コードの一部のみ示します(データベースから取得するコードは省略) コメント■■部分がレスポンスのプロパティ["data"]になります

GETリクエスト処理
@app.route(APP_ROOT + "/getcurrentdata", methods=["GET"])
def getcurrentdata():
    """メールアドレスと測定日付から健康管理データを取得するリクエスト

    :param: request parameter: ?emailAddress=user1@examples.com&measurementDay=2023-0-03
    :return: JSON形式(健康管理DBから取得したデータ + 気象センサーDBから取得した天候)
    """
    if app_logger_debug:
        app_logger.debug(request.path)
        app_logger.debug(request.args.to_dict())

    # 1.リクエストパラメータチェック
    # 1-1.メールアドレス
    emailAddress = request.args.get("emailAddress")
    if emailAddress is None:
        abort(BadRequest.code, _set_errormessage("462,Required EmailAddress."))
    # 1-2.測定日付
    measurementDay = request.args.get("measurementDay")
    if emailAddress is None:
        abort(BadRequest.code, _set_errormessage("463,Required MeasurementDay."))

    # PersonテーブルにemailAddressが存在するか
    personId = _get_personid(emailAddress)
    if personId is None:
        abort(BadRequest.code, _set_errormessage("461,User is not found."))

    # 健康管理DBと気象センサーDBからデータ取得する
    selector = Selector(Cls_sess_healthcare, Cls_sess_sensors, logger=app_logger)
    # 健康管理データ取得
    healthcare_dict: Dict = selector.get_healthcare_asdict(emailAddress, measurementDay) # ■■ 辞書オブジェクト
    if app_logger_debug:
        app_logger.debug(f"Healthcare: {healthcare_dict}")    
    if healthcare_dict:
        healthcare_dict["emailAddress"] = emailAddress
        healthcare_dict["measurementDay"] = measurementDay
        # 天気状態取得
        weather_dict = selector.get_weather_asdict(measurementDay)    # ■■ 辞書オブジェクト 
        if app_logger_debug:
            app_logger.debug(f"Weather: {weather_dict}")    
        if weather_dict:
            healthcare_dict["weatherData"] = weather_dict             # ■■ 健康管理データ辞書オブジェクトに天候状態を追加する
        else:
            # 天候がなければ未設定
            healthcare_dict["weatherData"] = None
        
        return make_getdata_success(healthcare_dict)                  # ■■ JSONレスポンスを返却する
    else:
        abort(NotFound.code, _set_errormessage("Data is not found."))


def make_getdata_success(json_dict: Dict) -> Response:
    """
    データ取得処理OKレスポンス
    :param json_dict: JSON出力用辞書
    :return: Response
    """
    resp_obj: Dict = {
        "status": {
            "code": 0, "message": "OK"},
        "data": json_dict                   # ■■ ["data"]プロパティに辞書オブジェクト(登録データ)を設定                
    }
    return _make_respose(resp_obj, 200)


def make_register_success(email_address: str, measurement_day: str) -> Response:
    """
    登録処理OKレスポンス
    :param email_address: メールアドレス
    :param measurement_day 測定日付
    :return: Response
    """
    resp_obj: Dict = {                      # ■■ レスポンス用辞書オブジェクト
        "status":
            {"code": 0, "message": "OK"},
        "data": {
            "emailAddress": email_address,
            "measurementDay": measurement_day
        }
    }
    return _make_respose(resp_obj, 200)


# ■■ エラーハンドラー: abort()関数から呼び出しされる
@app.errorhandler(BadRequest.code)
@app.errorhandler(Forbidden.code)
@app.errorhandler(NotFound.code)
@app.errorhandler(Conflict.code) # IntegrityError (登録済み)
@app.errorhandler(InternalServerError.code)
def error_handler(error: HTTPException) -> Response:
    app_logger.warning(f"error_type:{type(error)}, {error}")
    resp_obj: Dict[str, Dict[str, Union[int, str]]] = {
        "status": {"code": error.code, "message": error.description["error_message"]}  # ■■ウォーニングステータス 
    }
    return _make_respose(resp_obj, error.code)
メニューページへ
戻る