마이의 개발 블로그

[Java] 기상청 단기예보 API 데이터 가공하기 (JSONParser) 본문

개발지식/Java

[Java] 기상청 단기예보 API 데이터 가공하기 (JSONParser)

개발자마이 2022. 2. 16. 23:57
반응형

배경

개인 프로젝트로 React.js + SpringBoot 조합으로 일기예보 서비스를 만들어보고 있습니다. 이 프로젝트는 기존 학원 팀프로젝트에서 사용했던 Spring Legacy Project (JSP + Spring)를 벗어나 다른 기술, 특히 학습해놨던 React.js를 한 번 사용해보는게 목적이어서 프론트와 백엔드를 분리하고 간단하게 작업해볼 요량으로 시작했던 프로젝트였습니다. 근데 생각보다 기상청 단기예보 API 사용법을 숙지하고 데이터를 내가 필요한 방향으로 가공하는 데에 시간을 많이 사용하게 되어 사실상 React보다도 API를 공부하는 느낌이 더 강하게 드는 것 같네요.

기상청 단기예보 API

기상청 단기예보 API는 호출방법에 따라 아래 네 개 유형의 데이터를 보내줍니다.

  - 초단기실황조회

  - 초단기예보조회

  - 단기예보조회

  - 예보버전조회

 

오늘은 이 중에서 초단기실황조회를 호출하여 받아온 JSON 스트림을 JSONParser를 통해 어떻게 가공하는지 알아보려고 합니다(connection생성 및 API호출 부분은 생략).

가공 방법

1. JSON 스트림 받아오기

  - 기존에 생성된 HttpURLConnection 객체 connection에서 getInputStream() 메서드를 호출하여 스트림을 받아오기

  - StringBuilder 클래스의 append() 메서드를 활용하여 저장 후 toString() 메서드로 String 변환 후 리턴

BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();

String temp = "";

while((temp = br.readLine()) != null) {
    sb.append(temp);
}

br.close();

return sb.toString();

2. JSONParser 클래스를 이용하여 JSON 스트림 가공하기

  - API에서 리턴타입을 JSON으로 지정하면 이런 식으로 스트림이 들어옴

  - 보통 이렇게 한 줄로 쭉 표기되고, 크롬 익스텐션 등을 사용해서 보기 좋게 바꿀 수도 있음

{"response":{"header":{"resultCode":"00","resultMsg":"NORMAL_SERVICE"},"body":{"dataType":"JSON","items":{"item":[{"baseDate":"20220216","baseTime":"1100","category":"PTY","nx":55,"ny":127,"obsrValue":"0"},{"baseDate":"20220216","baseTime":"1100","category":"REH","nx":55,"ny":127,"obsrValue":"35"},{"baseDate":"20220216","baseTime":"1100","category":"RN1","nx":55,"ny":127,"obsrValue":"0"},{"baseDate":"20220216","baseTime":"1100","category":"T1H","nx":55,"ny":127,"obsrValue":"-4.4"},{"baseDate":"20220216","baseTime":"1100","category":"UUU","nx":55,"ny":127,"obsrValue":"3.4"},{"baseDate":"20220216","baseTime":"1100","category":"VEC","nx":55,"ny":127,"obsrValue":"257"},{"baseDate":"20220216","baseTime":"1100","category":"VVV","nx":55,"ny":127,"obsrValue":"0.8"},{"baseDate":"20220216","baseTime":"1100","category":"WSD","nx":55,"ny":127,"obsrValue":"3.5"}]},"pageNo":1,"numOfRows":1000,"totalCount":8}}}

 - 실제로 사용해야하는 일기예보 관련 데이터들은 response - body - items 안에 들어있는 item 객체 배열에 저장되어있는데, 여기까지 접근하기 위해서는 JSONParser를 이용한 파싱이 필요함

//JSON 객체를 item 단계까지 파싱하기
JSONParser jsonParser = new JSONParser(); 
JSONObject jsonObject =	(JSONObject) jsonParser.parse(stream);

JSONObject response = (JSONObject) jsonObject.get("response");
JSONObject body = (JSONObject) response.get("body");
JSONObject items = (JSONObject) body.get("items");

JSONArray jsonArray = (JSONArray) items.get("item"); 

return jsonArray;

- 마지막 줄에서 JSONArray 객체를 사용한 이유는, item 하위의 객체들이 배열로 이루어져있기 때문임

- item단계까지 파싱하고 난 후 jsonArray를 출력해본 결과 예시

[{"obsrValue":"0","baseDate":"20220216","nx":57,"ny":128,"category":"PTY","baseTime":"2300"},
 {"obsrValue":"-998","baseDate":"20220216","nx":57,"ny":128,"category":"REH","baseTime":"2300"},
 {"obsrValue":"0","baseDate":"20220216","nx":57,"ny":128,"category":"RN1","baseTime":"2300"},
 {"obsrValue":"-8.4","baseDate":"20220216","nx":57,"ny":128,"category":"T1H","baseTime":"2300"},
 {"obsrValue":"2.4","baseDate":"20220216","nx":57,"ny":128,"category":"UUU","baseTime":"2300"},
 {"obsrValue":"319","baseDate":"20220216","nx":57,"ny":128,"category":"VEC","baseTime":"2300"},
 {"obsrValue":"-2.7","baseDate":"20220216","nx":57,"ny":128,"category":"VVV","baseTime":"2300"},
 {"obsrValue":"3.7","baseDate":"20220216","nx":57,"ny":128,"category":"WSD","baseTime":"2300"}]

3. 가공된 데이터에서 필요한 데이터 추출하기

  - 위 단계까지는 네 가지 조회 서비스의 공통적인 부분이라 별도로 메서드를 분리하여 재사용하게끔 작성

  - 이제 jsonArray를 탐색하며 원하는 값을 추출하면 되는데, 나는 dto 객체를 별도로 선언하여 그 안에 필요한 값을 넣어주어 리턴하게끔 설계했기에 이런식으로 작성함

//배열을 돌며 필요한 카테고리 코드에 해당하는 값을 dto에 넣어주기
for(int i=0; i<jsonArray.size(); i++) {
			
    JSONObject item = (JSONObject) jsonArray.get(i);

    if(item.get("category").toString().equals("PTY")) {
        dto.setPTY(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("REH")) {
        dto.setREH(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("RN1")) {
        dto.setRN1(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("T1H")) {
        dto.setT1H(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("UUU")) {
        dto.setUUU(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("VEC")) {
        dto.setVEC(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("VVV")) {
        dto.setVVV(item.get("obsrValue").toString());
    }else if(item.get("category").toString().equals("WSD")) {
        dto.setWSD(item.get("obsrValue").toString());
    }
}

- 프론트(리액트)에서 axios를 사용하여 백엔드(스프링)를 호출하고, 그 결과값을 콘솔창에 출력한 내용

sky는 초단기예보서비스를 이용해 추출함

Note

단기예보 API의 데이터는 받아오는 것도 일이지만 이후에 API 문서를 참고하여 데이터를 로직에따라 가공해서 사용하는 일에 더 많은 시간이 든다는 사실을 나중에야 알았습니다. 해외의 기상 예보 API들은 가공이 끝나 사용하기 편리하게끔 데이터를 던져주던데 왜 이렇게 사용하기 불편하게 가공 전의 데이터를 그대로 던져주는지는 아직도 의문이긴 합니다. 어차피 가공하는 로직은 바뀔 게 없기때문에 사실상 이 API를 사용하는 유저들이 불필요한 중복 코드를 재생산해내게끔 하는 것이기 때문입니다. 만약 기상 예보 API를 사용하려는 사람이 있다면 해외 API와 먼저 비교하여 뭐가 더 편리한지 한 번 더 생각해보길 권장합니다.

반응형
Comments