| fcstValue 한 칸에 담긴 날씨 | 기상청 단기예보는 category와 fcstValue로 모든 예보 항목을 표현한다. POP는 숫자 문자열, SKY는 코드 문자열, PCP는 "강수없음" 같은 범주 문자열이다. 클라이언트가 fcstValue를 숫자로 억지 변환하다가 타입 불일치 에러를 숨기고, 일부만 채워진 불완전한 구조체로 의미를 잃어버린다. 병렬 처리를 우아하게 구현하는 Go의 동시성 모델을 실전 패턴으로 정리한다. |
| 세 가지 치명적 착각 | | • 단기예보 항목의 fcstValue는 문자열이다. 숫자처럼 보여도 "30"이다. | | • ,string 태그로 숫자 변환을 억지로 걸면 "30"은 통과하지만 "강수없음"에서 실패한다. | | • 에러를 _로 무시하면 일부만 채워진 불완전한 구조체가 남고, 실패한 필드는 zero value(0)로 보여 실제 데이터와 구분할 수 없다. |
{ "category": "POP", "fcstValue": "30", "fcstDate": "20250401", "fcstTime": "0900" } | • | POP: 강수확률 (0~100 숫자 문자열) | | • | SKY: 하늘상태 (1=맑음, 3=구름많음, 4=흐림) | | • | PCP: 1시간 강수량 ("강수없음", "1.0mm 미만", "30.0~50.0mm") |
| fcstValue는 숫자가 아니다 Go의 encoding/json은 quoted string "30"을 int 필드에 자동으로 넣지 않는다. ,string 태그는 숫자 문자열을 통과시키지만, "강수없음" 같은 범주 문자열에서 UnmarshalTypeError를 발생시킨다. |
forecast_dto.go type Forecast struct { FcstValue int `json:"fcstValue,string"` } silent_kma.go func parseItems(itemsRaw []byte) { var items []Forecast err := json.Unmarshal(itemsRaw, &items) if err != nil { _ = err // 에러를 무시하면? } // 일부 값만 채워진 불완전한 구조체가 남음 // 실패한 필드는 zero value(0)로 보임 } | 에러 무시의 결과 json.Unmarshal은 타입 불일치 필드를 건너뛰고 가능한 만큼 계속한다. 하지만 문제 필드 뒤의 나머지 필드까지 모두 채워진다고 보장하지 않는다. 에러를 무시하면 불완전한 구조체가 정상 데이터처럼 보이고, 0이 실제 '강수확률 0%'인지 '파싱 실패 흔적'인지 구분할 수 없다. |
kma_parser.go type RawItem struct { Category string `json:"category"` FcstValue string `json:"fcstValue"` } type POP struct { Value int Valid bool } func ParsePOP(item RawItem) (POP, error) { if item.Category != "POP" { return POP{Valid: false}, nil } if item.FcstValue == "" { return POP{}, fmt.Errorf("POP 값 누락") } v, err := strconv.Atoi(item.FcstValue) if err != nil { return POP{}, fmt.Errorf("POP 파싱 실패: %q", item.FcstValue) } return POP{Value: v, Valid: true}, nil } | 공식 데이터 규격 공공데이터포털의 단기예보 조회서비스 명세와 데이터위키의 코드값 테이블을 함께 참고해야 한다. |
| API 응답에서 `items` 배열을 추출한다. |
| `fcstValue`를 `string`으로 보존하는 `RawItem` 슬라이스로 언마샬한다. |
| `category`에 따라 적절한 파서(POP, SKY, PCP 등)를 호출한다. |
| 파싱 실패 시 에러를 로깅하고, 필요한 경우 해당 항목을 명시적으로 처리한다. |
| zero value와 실제 결측치를 구분하는 `Valid` 필드를 함께 저장한다. |
| | Before `FcstValue int,string` + 에러 무시 → `"30"`과 `"1"`은 숫자로 들어가지만 POP/SKY의 의미가 섞이고 `"강수없음"`은 0처럼 보임 → 의미 소멸 | | | After `RawItem` 보존 + category별 파서 → 각 항목의 의미를 살리고 실패를 명시적으로 기록 |
| 기상청 API가 이상한 JSON을 내려보내는 게 아니다. 단일 문자열 fcstValue에 여러 종류의 값을 담는 설계를, 클라이언트가 ,string 태그로 숫자로 억지 변환하다가 타입 불일치 에러와 의미 손실이 발생한 것이다. 해결은 원본 문자열을 먼저 보존하고, category별 파싱 규칙을 적용하며, 에러를 절대 숨기지 않는 것이다. |
|
댓글 0