Java에서 JSON BigDecimal 직렬화/역직렬화 구현 방법

danbi lee·2025년 2월 20일

테스트 케이스가 실패했다. BigDecimal을 사용한 숫자의 표현이 달라서였다.
기대값은 지수표기법이고, 결과값은 일반숫자형...
데이터를 저장하고 출력할땐 문제가 없었는데 테스트 케이스에서는 왜 오류가 났을까?

JSON

JavaScript Object Notation
JSON은 JavaScript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 표준 포맷이다.

{
	"name": "치비",
  	"age" : 11
}
  • 데이터를 교환하기 위한 문자열
  • 오직 큰 따옴표로 묶인 문자열만 프로퍼티로 사용 됨. 작은 따옴표나 메서드는 사용 불가
  • JavaScript 객체 문법과 유사하지만 거의 모든 프로그래밍 언어에서 지원
  • 데이터 타입으로 문자열, 숫자, 배열, Boolean, 객체를 지원함

JSON의 숫자 표현

숫자의 표현 형식을 엄격하게 제한하지 않는다.
그렇기에 테스트에서 보여준 것과 같이 동일한 숫자라도 다양한 형태로 표현될 수 있다.
(8진수나 16진수 표기는 허용되지 않음)

{
  "integer": 123,           // 정수
  "float": 123.45,         // 일반적인 소수점 표기
  "scientific": 1.2345e2,   // 지수 표기법
  "negative": -123.45      // 음수
}

여기서 일반 형식(고정소수점)은 소수점의 위치가 고정된 형태이고, 지수 형식(부동소수점)은 소수점의 위치가 움직이는 형태를 말한다.
BigDecimal 관련 이슈와 연결을 해보면 다음과 같다.

BigDecimal num1 = new BigDecimal("123.45");    // 일반 표기
BigDecimal num2 = new BigDecimal("1.2345E2");  // 지수 표기

// 둘 다 같은 값을 나타내지만 문자열 표현이 다름
System.out.println(num1);         // 123.45
System.out.println(num2);         // 1.2345E2
System.out.println(num1.equals(num2));  // true

직렬화와 역직렬화

@Serializable
data class Cat(
    val name: String,
    val age: Int
)

fun kotlinxSerializationExample() {
    val cat = Cat("치비", 11)
    
    // 직렬화
    val json = Json.encodeToString(cat)
    
    // 역직렬화
    val deserializedPerson = Json.decodeFromString<Cat>(json)
}

직렬화(Serialization)

직렬화는 객체나 데이터를 저장하거나 전송할 수 있는 형태로 변환하는 프로세스를 말한다.

직렬화 결과:
{
  "name" : "치비",
  "age" : 11,
  "birth_date" : "2013-11-24"
}

역직렬화(Deserialization)

역직렬화는 반대의 과정으로, 데이터나 객체를 원래의 구조로 복구하는 프로세스를 말한다.

역직렬화 결과:
Cat(name=치비, age=12, birthDate=2013-11-24)

언제쓸까?

  • 네트워크 통신: 객체를 직렬화하여 네트워크를 통해 전송하고, 수신 측에선 해당 객체를 역직렬화하여 사용
  • 데이터 저장: 직렬화를 통해 객체를 바이트 스트림으로 변환하고, 이를 저장소에 기록. 나중에 이 데이터를 역직렬화해 원래의 객체 상태로 복원
  • 캐싱: 캐싱된 데이터를 직렬화하여 효율적으로 저장하고, 필요할때 빠르게 역직렬화하여 사용

테스트 실패 원인

내가 실행한 테스트는 캐싱 처리에 대한 내용이었다.
일반적으로 객체를 JSON으로 직렬화하여 캐시에 저장을 하고, 캐시에서 데이터를 가져와 역직렬화를 한다.

@Test
fun `캐시 저장 및 조회 테스트`() {
    // given
    val price = BigDecimal("1.2345E3")
    val product = Product("item", price)

    // when
    cacheService.save("key", product)
    val cached = cacheService.get("key")

    // then
    assertEquals(price, cached.price) // 여기서 실패
    // price: 1.2345E3 (기대값)
    // cached.price: 1234.5 (실제값)
}

직렬화 시 BigDecimal이 지수 표기법으로 저장이 되었고역직렬화 시 일반 숫자형으로 변환이 되어 발생한 문제였다.

val price: BigDecimal = price.stripTrailingZeros()

테스트 케이스에서도 커스텀 Serializer를 사용해서 타입을 맞추거나, compareTo()를 사용하여 해결하면 된다!

런타임 오류

Runtime Error는 실행될때 발생하는 오류인데, 테스트 케이스에서 런타임 오류가 발생하지 않은 이유는 뭘까?

런타임 오류 종류

  • NullPointerException (null 객체 참조)
  • ArrayIndexOutOfBoundsException (배열 범위 초과)
  • ClassCastException (잘못된 타입 캐스팅)
  • NumberFormatException (잘못된 숫자 형식)

캐시에서 데이터를 가져올 때 역직렬화는 잘 되었기에 BigDecimal 타입 캐스팅 오류는 발생하지 않았다. 직렬화/역직렬화 과정에서 표현 방식이 다른거지 숫자 자체는 BigDecimal이기 때문이다.


MDN - JSON으로 작업하기
IBM - JSON 형식 특성 및 데이터 유형 변환

profile
계속해서 보완중

0개의 댓글