Gson에서 kotlinx-serialization로 갈아탄 이유

상상코딩·2022년 2월 14일
1

안드로이드

목록 보기
9/21

모델의 널러블/논널 타입을 명확하게 보장해주기 때문이다.

Gson의 문제점

: non-null 변수에 null이 들어감;;

data class Employee(
    val id: Int,
    val name: String,
)

여기서 {"name":"Marketing"} 이런식으로 보내면 Employee(null, “Sangeun”) 와 같이 들어감.

여기서 포인트는 no 변수가 non-null이라는 것. 개무시하고 null이 들어가는데, 문법상 저 변수는 non-null이기 때문에 코드 작성시 널체크가 무의미하다고 lint가 경고를 해댐~

그럼 kotlinx-serialization은? (장점이나 이유 말고도 개발할 때 알아야하는 특징들을 열거했다.)

디폴트 밸류보다 응답값이 더 중요함.

data class Employee(
    val id: Int,
    val name: String = "상은",
)

이렇게 디폴트 밸류를 해놔도 응답에서 {”id”:13, "name":"Sangeun"} 이렇게 준다면 Employee(13, “Sangeun”)이 됨.

타입을 중요하게 생각함.

→ non-null변수에 null이 들어왔을때 에러 (Gson이랑의 차이점)

data class Employee(
    val id: Int,
    val name: String, // null이 들어오면 에러뱉음.
)

이렇게 디폴트 밸류를 해놔도 응답에서 {”id”:13, "name":"null"} 이렇게 준다면 응답이 우선이니까 null이 들어가겠죠? 근데 타입을 중요하게 생각하는 kotlinx-serialization은 에러를 뱉음!(name은 non-null변수이므로)

→ 이때는 val json = Json { coerceInputValues = true } 옵션을 이용하면 됩니다용 (코드 하단 참조)

없는 키를 받으면 에러를 뱉음

data class Employee(
    val id: Int,
    val name: String,
)

{"no":13,"name":"상은","nickName":"Wow!!!"} 여기서 nickName처럼 모델에 없는 키값이 들어온다면 에러를 뱉는다. val json = Json { ignoreUnknownKeys = true } 옵션을 쓰면 댐!

주의할 점

요청시와 응답시 디폴트밸류를 처리하는게 조금 다름.

요청 → 디폴트밸류가 안먹힘

data class Employee(
    val id: Int,
    val name: String = "상은",
)

Employee(id = 13)으로 보내면 {”id”:13} 이렇게 보내는 셈.

→ 이때는 val json = Json { encodeDefaults = true } 옵션을 이용하면 됩니다용 (코드 하단 참조)

응답 → 디폴트밸류가 먹힘

data class Employee(
    val id: Int,
    val name: String = "상은",
)

{”id”:13} 이렇게만 받아도 Employee(13, “상은”)으로 받는셈이 된다!

  • code
    package com.example.kotlinxserializationexample
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import com.example.kotlinxserializationexample.model.Dept
    import com.example.kotlinxserializationexample.model.Dept2
    import com.example.kotlinxserializationexample.model.Employee
    import kotlinx.serialization.ExperimentalSerializationApi
    import kotlinx.serialization.decodeFromString
    import kotlinx.serialization.encodeToString
    import kotlinx.serialization.json.Json
    
    @ExperimentalSerializationApi
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            makeDeptToJson()
            makeJsonToDept()
            makeDept2Json()
            makeDeptIgnoreJson()
            makeCoerceJson()
            makeEncodeDefaultJson()
        }
    
        private fun makeDeptToJson() {
            val dept = Dept(1, "Marketing", "USA/Seattle")  // data class 생성
            val deptJson = Json.encodeToString(dept)    // data class -> Json 으로 변환
            Log.d(
                TAG,
                deptJson
            )    // D/MainActivity: {"no":1,"name":"Marketing","location":"USA/Seattle"}
        }
    
        private fun makeJsonToDept() {
            val dept = Dept(1, "Marketing", "USA/Seattle")
            val deptJson = Json.encodeToString(dept)
            val deptFromJson = Json.decodeFromString<Dept>(deptJson) // json string -> data class 로 변환
            Log.d(TAG, deptFromJson.toString()) // Dept(no=1, name=Marketing, location=USA/Seattle)
        }
    
        private fun makeDept2Json() {
            val employees = listOf(
                Employee(0, "Smith"),
                Employee(1, "Mike"),
                Employee(2, "John")
            )
    
            val dept = Dept2(1, "Marketing", "USA/Seattle", employees)
            val deptJson = Json.encodeToString(dept)
            Log.d(TAG, deptJson)
            /**
             *  { "no":1,
             *    "name":"Marketing",
             *    "location":"USA/Seattle",
             *    "employees":
             *        [{"no":0,"name":"Smith"},
             *         {"no":1,"name":"Mike"},
             *         {"no":2,"name":"John"}
             *        ]
             *   }
             */
    
            val deptFromJson = Json.decodeFromString<Dept2>(deptJson)
            Log.d(TAG, deptFromJson.toString())
            /**
             *  Dept2(
             *      no=1,
             *      name=Marketing,
             *      location=USA/Seattle,
             *      employees=
             *          [Employee(no=0, name=Smith),
             *           Employee(no=1, name=Mike),
             *           Employee(no=2, name=John)]
             *  )
             */
    
            // Json Option 1. pretty Json
            val prettyJson = Json { prettyPrint = true }
            val deptPrettyJson = prettyJson.encodeToString(dept)
            Log.d(TAG, deptPrettyJson)
    
            val deptFromPrettyJson = prettyJson.decodeFromString<Dept2>(deptPrettyJson)
            Log.d(TAG, deptFromPrettyJson.toString())
        }
    
        private fun makeDeptIgnoreJson() {
            val json = Json { ignoreUnknownKeys = true }
            val deptJson = """ {"no":"1","name":"Marketing","location":"USA/Seattle","nickName":"Wow!!!"} """
            val deptFromJson = json.decodeFromString<Dept>(deptJson)
            Log.d(TAG, deptFromJson.toString()) // ept(no=1, name=Marketing, location=USA/Seattle)
        }
    
        /**
         * Coercing input values (decoding: json -> data class)
         * 강력한 type check 를 느슨하게 조정 (default 값 할당)
         * - non null property 에 null 이 입력될 경우
         * - enum 값을 담는 property 에 정해지지 않은 enum 이 들어오는 경우
         */
        private fun makeCoerceJson() {
            val json = Json { coerceInputValues = true }
            val deptJson = """ {"no":"1","name":"Marketing","location":null} """
            val deptFromJson = json.decodeFromString<Dept>(deptJson)
            Log.d(TAG, deptFromJson.toString())
            // default 값이 model 에 지정되어 있어야 null 이 들어와도 default 값으로 들어감
            // D/MainActivity: Dept(no=1, name=Marketing, location=default)
            // ** coerceInputValues 옵션이 설정되어있지 않으면? 에러남
        }
    
        /**
         * Encoding defaults (encoding: data class -> json)
         * 값이 설정되지 않도라도 data class 의 default 값이 json 에 기본값으로 출력되도록 하기 위해 사용
         */
        private fun makeEncodeDefaultJson() {
            val json = Json { encodeDefaults = true }
            val dept = Dept(no = 1, name = "Marketing")
            val deptFromJson = json.encodeToString(dept)
            Log.d(TAG, deptFromJson)
            // Dept 는 no, name 만 선언하고 이상태로 json 으로 encoding 하면 data class 의 location default 값이 나옴
            // D/MainActivity: {"no":1,"name":"Marketing","location":"default"}
            // ** encodeDefaults 옵션이 설정되어있지 않으면? 값이 없는 property 는 json 에 포함되지 않음 -> D/MainActivity: {"no":1,"name":"Marketing"}
        }
    
        companion object {
            const val TAG = "MainActivity"
        }
    }
    package com.example.kotlinxserializationexample.model
    
    import kotlinx.serialization.Serializable
    
    @Serializable
    data class Dept(
        val no: Int,
        val name: String,
        val location: String = "default"
    )
    
    @Serializable
    data class Dept2(
        val no: Int,
        val name: String,
        val location: String,
        val employees: List<Employee>
    )
profile
히히낙낙

0개의 댓글