ABOUT ME

iswaterwet 님의 블로그 입니다.

Today
Yesterday
Total
  • json/gson/moshi
    청년취업사관학교 next gen 안드로이드 개발 bootcamp 2024. 11. 7. 17:59

     
    데이터 처리 방식
     
    1. JSON(JavaScript Object Notation)
     
    json은 데이터를 표현하기 위한 경량 데이터 형식.
    문자열 형태로 데이터 주고받기 가능.
    주로 서버와 클라이언트 간 데이터 교환에 사용
     
    { "name": "Alice", "age": 25 }
     
    kotlin이나 Java에서 JSON을 쉽게 다루기 위해 JSON을 파싱하고 직렬화할 수 있는 라이브러리들이 필요함.
     
    2. Gson
     
    구글에서 제공하는 JSON 라이브러리. Java와 Kotlin에서 JSON을 객체로 변환하거나 객체를 JSON 형식으로 직렬화할 수 있게 도와줌.

    //객체를 JSON으로 직렬화하는 예시
    import com.google.gson.Gson
    
    data class Product(val id: Int, val name: String, val price: Double)
    
    fun main() {
        val product = Product(id = 101, name = "Laptop", price = 999.99)
        val gson = Gson()
    
        // 객체 -> JSON 직렬화
        val json = gson.toJson(product)
        println(json) // 출력: {"id":101,"name":"Laptop","price":999.99}
    }

    객체와 JSON 간의 변환이 쉬움. 기본적으로 *직렬화/역직렬화 과정에서 코드가 간결해짐.
    직렬화(Serialization): 객체를 특정 형식의 데이터로 변환하는 과정. 주로 메모리 상의 객체 데이터를 파일, 데이터베이스, 또는 네트워크 전송이 가능한 형식으로 변환할 때 사용. Gson에서는 객체를 JSON 형식의 문자열로 변환하는 것을 직렬화라고 함.
    역직렬화(Deserialization): 직렬화된 데이터를 원래의 객체 형태로 되돌리는 과정. 예를 들어 JSON 형식으로 표현된 문자열을 Gson을 사용해 원래의 java 객체로 변환하는 것
     
    <특징>
    직렬화와 역직렬화 지원(fromJson()과 toJson()).
    속성 이름이 일치하지 않아도 @SerializedName 어노테이션을 통해 JSON 속성 이름과 매핑 가능.
    유연한 *사용자 정의 컨버터 작성 가능.
    사용자 정의 컨버터(User-Defined converter): Json 직렬화 및 역 직렬화 과정에서 특정 데이터타입에 대해 맞춤형 변환 규칙을 설정할 수 있도록 해주는 도구. 예) Gson에서는 JsonSerializer/JsonDeserializer/TypeAdapter클래스를 사용해 사용자 정의 컨버터를 만들 수 있음. 이 컨버터를 사용하면 기본 변환 규칙과 다르게, 특정 요구사항에 맞춘 변환 방식을 정의 가능.
     
    이유: Gson은 객체 변환 과정을 제어할 수 있도록 TypeAdapter나 JsonSerializer와 같은 클래스들을 제공함. 이 클래스들을 통해 개발자가 특정 필드, 클래스, 데이터 형식에 대해 변환 규칙을 커스터마이징할 수 있음. 예) 날짜 형식을 특정 방식으로 변환 / 특정 필드만 포함되게 하기(필드 생략/추가), json데이터가 객체 구조와 일치하지 않는 경우/ 여러 필드를 조합하거나 변형하여 하나의 객체 필드로 변환할 때 등.
    사용자 정의 컨버터?  

    예시: Gson으로 날짜 형식을 사용자 정의 컨버터로 변환

    예를 들어, Date 타입을 "yyyy-MM-dd" 형식의 문자열로 직렬화하고 싶을 때 사용자 정의 컨버터를 작성할 수 있습니다.

    import com.google.gson.*
    import java.lang.reflect.Type
    import java.text.SimpleDateFormat
    import java.util.Date
    
    // 사용자 정의 컨버터 클래스
    class DateSerializer : JsonSerializer<Date>, JsonDeserializer<Date> {
        private val dateFormat = SimpleDateFormat("yyyy-MM-dd")
    
        // Date -> JSON (직렬화)
        override fun serialize(src: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
            return JsonPrimitive(dateFormat.format(src))
        }
    
        // JSON -> Date (역직렬화)
        override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Date {
            return dateFormat.parse(json?.asString)
        }
    }
    
    fun main() {
        val gson = GsonBuilder()
            .registerTypeAdapter(Date::class.java, DateSerializer()) // 사용자 정의 컨버터 등록
            .create()
    
        // Date 객체를 JSON으로 직렬화
        val date = Date()
        val json = gson.toJson(date)
        println(json) // 출력 예시: "2024-11-14"
    
        // JSON을 Date 객체로 역직렬화
        val dateFromJson = gson.fromJson(json, Date::class.java)
        println(dateFromJson)
    }

    위 코드에서 DateSerializer는 Date 객체를 "yyyy-MM-dd" 형식의 문자열로 직렬화하고, JSON 문자열을 Date 객체로 역직렬화하는 사용자 정의 컨버터입니다. GsonBuilder의 registerTypeAdapter() 메서드를 통해 컨버터를 등록하여 Gson이 이 변환 규칙을 따르도록 합니다.
     
    단점: 자바 리플렉션을 사용해 실행 시점에 성능이 떨어질 수 있음.
    이유: java 리플렉션은 클래스의 메타 데이터(필드, 메서드 등)를 런타임에 참조해 객체의 속성에 접근하거나 조작하는 기능임. 이 과정이 컴파일 타임이 아닌 런타임에 수행되기 대문에 성능 상의 오버헤드가 발생. 리플렉션을 통해 필드를 찾거나 메서드를 호출하는 과정은 일반 메서드 호출에 비해 느릴 수있음.

    // 예시 코드
    data class User(val name: String, val age: Int)
    val userJson = """{"name":"Alice","age":25}"""
    val user = Gson().fromJson(userJson, User::class.java) // JSON -> 객체 변환
    val jsonString = Gson().toJson(user) // 객체 -> JSON 변환

     
    3. Moshi
     
    *Square에서 개발한 JSON 라이브러리. Gson과 비슷하게 JSON과 객체 간의 변환을 지원하나 Kotlin에 최적화된 기능을 제공함.
    *Square: 결제 및 금융 관련 소프트웨어와 하드웨어 솔루션을 제공하는 미국 기업.

    import com.squareup.moshi.Moshi
    import com.squareup.moshi.Json
    
    // 데이터 클래스 정의
    data class User(
        @Json(name = "name") val name: String,
        @Json(name = "age") val age: Int
    )
    
    fun main() {
        // Moshi 인스턴스 생성
        val moshi = Moshi.Builder().build()
        val adapter = moshi.adapter(User::class.java)
    
        // JSON -> 객체 변환
        val userJson = """{"name": "Alice", "age": 25}"""
        val user = adapter.fromJson(userJson)
        println(user) // 출력: User(name=Alice, age=25)
    
        // 객체 -> JSON 변환
        val userObject = User("Bob", 30)
        val jsonString = adapter.toJson(userObject)
        println(jsonString) // 출력: {"name":"Bob","age":30}
    }

    Kotlin의 sealed class, data class 등과 잘 호환되며 null처리와 같은 타입 안전성에서도 강점을 가짐.
    : sealed class는 특정 클래스 계층을 제한적으로 구성하여 하위클래스의 종류가 고정되므로, moshi는 해당 클래스를 기반으로 명확한 json구조를 생성 및 변환할 수 있다.
    data class는 자동으로 equals, hashCode, toString 등을 생성하고, json 매핑에 필요한 copy나 componentN 함수도 제공하여 json 데이터를 쉽게 다룰 수 있게 해줌.
    moshi는 kotlin의 타입 시스템을 활용하여 타입 불일치 오류를 사전에 방지한다.
    예를 들면, moshi는 json의 특정 필드가 nullable인지 아닌지, 미리 정의된 클래스의 하위 타입인지 명확하게 검증하여 잘못된 변환을 막아준다. 이 때문에 개발자가 실수로 잘못된 데이터 형식을 다루지 않을 수 있어서 타입 안전성이 높다.
    <특징>
    @Json 어노테이션을 사용해 속성 이름 매핑 가능.

    import com.squareup.moshi.Json
    import com.squareup.moshi.Moshi
    
    data class Person(
    
    	//JSON의 "full_name"속성이 Kotlin의 name 속성과 매핑됨
        @Json(name = "full_name") val name: String,
        
        //JSON의 "age_years" 속성이 Kotlin의 age 속성과 매핑됨
        @Json(name = "age_years") val age: Int
    )
    
    val json = """{"full_name": "Alice", "age_years": 25}"""
    val moshi = Moshi.Builder().build()
    val adapter = moshi.adapter(Person::class.java)
    val person = adapter.fromJson(json)

    JSON 파서의 속도와 효율성이 우수하며, *프로가드와도 쉽게 호환됨.
    *프로가드: ProGuard는 안드로이드 앱에서 코드 최적화, 난독화, 축소 등을 제공하는 도구임. 코드 난독화는 리버스 엔지니어링을 어렵게 해 보안성을 높이고, 사용되지 않는 코드를 제거해 앱 크기를 줄이는 데 도움이 됨. 프로가드 설정 파일을 통해 특정 클래스와 메서드를 유지할 수 있도록 설정할 수도 있음.
    KotlinJsonAdapterFactory를 사용하면 Kotlin 특화 기능을 활용한 더 좋은 타입 안전성을 제공함.
    Moshi는 코틀린의 KotlinJsonAdapterfactory를 통해 nullable 및 non-nullable 타입을 구분함. sealed class 계층, data class의 자동 생성 메서드 등을 활용함. 그래서 moshi가 클래스와 json 간에 보다 정확한 매핑을 할 수 있도록 도와주며, kotlin의 타입 시스템에 맞춘 null 체크와 불변성을 보장하는 데 도움이 됨.
     
    단점: Gson에 비해 사용 빈도가 적어 *레거시 시스템과의 호환성이 제한될 수 있음.
    오랜 기간 사용된 gson이 여전히 가장 많이 사용되는 편임. 안드로이드 개발자 사이에서는 대략 gson이 60 ~ 70 %, moshi가 30~40%로 추정됨. 
    레거시 시스템: 기존에 사용되어 왔지만 현대적인 기준이나 기술에 비해 다소 구식이 된 시스템 또는 코드베이스를 의미.
     
     

Designed by Tistory.