어떤 정보를 주고 받을때 JSON를 자주 사용합니다. Golang은 JSON을 처리하기 위해 데이터를 JSON으로 Marshal, Unmarshal을 거칩니다. 이번 포스트에서는 큰 숫자값에 대해서 Marshal, Unmarshal시 값의 차이가 생겼던 경험과 해결에 대해서 적습니다.
API를 만드는 입장에서 연동하는 외부의 JSON의 모든 자료형을 처리해야 하는 경우가 많기 때문에 map[string]interface{}로 지정해서 map의 key는 string이고 value는 모든 자료형을 담을 수 있도록 interface{}로 지정합니다. 이 말은 vlaue의 자료형을 명시하지 않는다는 것과 같습니다. 이렇게 만들어진 데이터를 byte array로 marshal 작업을 통해 전송하는게 일반적입니다.
// 결과 map[int64:9223372036854775807] map[int64:9.223372036854776e+18]
문제 확인
문제점을 발견했나요? 9223372036854775807의 값이 JSON marshal/unmarshal을 거치니 9.223372036854776e+18로 표현되고있습니다. 분명 int64의 값을 담아 전송했고 받은 사람은 map[string]interface{}로 unmarshal했지만 Println의 호출된 결과가 달라진 것이죠. map안의 int64값을 확인하기 위해 int64로 형변환하여 확인한 결과는 아래와 같습니다.
// marshal var bytes []byte var err error if bytes, err = json.Marshal(mapObject); err != nil { fmt.Println(err.Error()) } fmt.Println(mapObject)
// decode d := json.NewDecoder(strings.NewReader(string(bytes))) d.UseNumber() var decodeMapObject map[string]interface{} if err := d.Decode(&decodeMapObject); err != nil { fmt.Println(err.Error()) } fmt.Println(decodeMapObject)
// 형변환 테스트 var castedMaxInt64 int64 if castedMaxInt64, err = decodeMapObject["int64"].(json.Number).Int64(); err != nil { fmt.Println(err.Error()) } fmt.Println(castedMaxInt64) }
1 2 3 4
// 결과 map[int64:9223372036854775807] map[int64:9223372036854775807] 9223372036854775807
이 방법이 decode 했을때도 값이 float64형태로 변하지 않고 int64 형변환도 문제없이 작동합니다. 데이터를 json.Number로 형변환 후 다시 Int64()를 호출해서 자료형을 변환해야 하지만 Overflow가 일어나는 것 보다 훨씬 낫군요. 이 방법으로 해결 방향을 잡았습니다.
결론
위의 문제는 프로젝트가 개발기간 중간에 발견한 문제입니다. 복잡한 로직이 한창 개발되는 상황에서 기초 함수에서 발견된 문제라 자칫 미궁으로 빠질수도 있었던 상황이였는데 정리해 두고 시간이 나는 지금에서야 블로그에 올리게 되었습니다. 항상 단위테스트를 통해 개발하는 습관을 가지고 경계값들도 꾸준히 체크한다면 사전에 방지할 수 있는 문제라고 생각됩니다.