응답 모델의 프로퍼티를 모두 Nullable
로 하면 안전한(좋은) 이유에 대해 설명해보고자 한다.
Gson을 사용하여 프로젝트를 진행하며 생겼던 이슈에 대해서도 함께 말할 것이다.
API 통신 중 아래와 같은 에러를 발견했다.
java. lang-NullPointerException: Parameter specified as non-null is null
non-null
타입인 positionId
매개변수에 null
이 들어왔다는 것이다.
모델 구조는 대충 아래와 같았다.
// DTO
data class UserDTO(
val number:String?,
val positionId:String
...
)
// Domain Model
data class User(
val number:String,
val positionId:String
)
오류를 보니 DTO -> Domain
모델로의 Mapping
과정에서 생겼다.
DTO
는 서버 스펙을 따랐지만, 실수로 nullable
인 positionId
를 non-null
로 선언했다.
non-null
타입인데 서버에서 null
이 내려왔으니 에러가 발생하는 건 당연한 것이었다.
하지만 에러가 발생한 위치가 이상했다.
위와 같은 상황이면 DTO
로 역직렬화를 할 때 에러가 생겼어야 하는건데, Domain model
로의 Mapper
함수에서 에러가 발생했다는 것이다.
아래와 같이 DTO
객체가 생성되었다는 것이다.
UserDTO(number:String = "1234", positionId:String = null)
분명히 non-null
타입임에도 positionId
의 값은 null
이 들어와있다.
테스트 코드로 재현해보겠다.
private val json = """
{
"id": 1,
"name": null,
"email": "alice@example.com"
}
"""
@Test
fun `Gson 역직렬화 테스트`() {
val user = gson.fromJson(json, GsonUser::class.java)
println(user)
}
data class GsonUser(
val id: Int,
val name: String,
val email: String,
)
// 결과
GsonUser(id=1, name=null, email=alice@example.com)
name
은 non-null
타입인데 null
이 들어온다.
kotlinx.serialization
의 경우는 다르다.
@Test
fun `Json 역직렬화 테스트`() {
val user = jsonFormatter.decodeFromString<JsonUser>(json)
println(user)
}
@Serializable
data class JsonUser(
val id: Int,
val name: String,
val email: String,
)
Gson과 달리 역직렬화 과정에서 에러가 발생한다.
왜 Gson은 non-null
타입에 null
값이 할당될 수 있는 것일까?
왜 kotlinx.serialization은 null
값이 할당되기 전에 에러가 일어날 수 있는걸까?
둘의 가장 큰 차이는 Gson은 내부 코드가 자바로 되어있고, kotlinx.serialization은 코틀린으로 되어있기 때문이다.
그렇기에 Gson은 코틀린의 non-null
타입을 이해하지 못한다.
이러한 이유로 Gson은 null
값을 non-null
타입에 역직렬화 할 때 에러없이 수행하는 것이다.
Gson은 기본적으로 리플렉션을 사용한다.
(kotlinx.serialization은 리플렉션을 사용하지 않고, 컴파일 플러그인을 사용한다고 합니다. 그 이유는 멀티플랫폼을 고려하여 설계가 되었기 때문이고, Reflection Kotlin/JVM 대상만 가능하기 때문이다.)
이번 상황은 null
에 대한 타입 선언을 실수한 제 탓이기도 하지만, 개발자 입장에서 이런 것들을 신경쓰기란 꽤나 번거로운 일이다.
개발하다보면 몇십개가 넘는 필드들을 마주할 텐데 하나하나 이 필드들의 Null
여부를 판단하기에는 쉽지 않은 일이다.
또한, 실수가 나올 수도 있고 서버측에서 공지없이 타입이 변경될 수도 있는 것이다.
그러므로 우리 모두 DTO
프로퍼티는 nullable
로 안전하게 다루도록 하자!