[TIL] ๐ŸŒผ24/04/09๐ŸŒผ#Moshi

1

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
77/100
post-thumbnail

[TIL] ๐ŸŒผ24/04/09๐ŸŒผ#Moshi

  • ํ”„๋กœ์ ํŠธ์—์„œ Json ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”์— Gson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์œผ๋‚˜, Gson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ Moshi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ปจ๋ฒ„ํŒ…ํ•˜๋Š”๊ฒŒ ์ข‹๊ฒ ๋‹ค๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•˜๋‹ค.
  • ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•˜์—ฌ, ์˜ค๋Š˜์€ Shared Preferences ๋ฐ์ดํ„ฐ ์ €์žฅ ๋กœ์ง์—์„œ Gson -> Moshi ์ปจ๋ฒ„ํŒ…์„ ํ•ด๋ณด์•˜๋‹ค.

Moshi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ๋จผ์ € Moshi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ์ด์œ ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜๋‹ค.

๐Ÿ“Œ์ฐธ๊ณ ์ž๋ฃŒ

Gson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • Gson ์ง๋ ฌํ™” ๋ฐฉ์‹:
    • reflection ์‚ฌ์šฉ: ๋Ÿฐํƒ€์ž„์— ๊ฐ์ฒด์˜ ํ•„๋“œ์— ์ ‘๊ทผํ•˜์—ฌ JSON์œผ๋กœ ์ง๋ ฌํ™”
    • ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ธ์‹: ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋Ÿฐํƒ€์ž„์— ํ™•์ธ
    • ์œ ์—ฐํ•œ ์‚ฌ์šฉ: ์ปค์Šคํ…€ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๊ตฌํ˜„์— ์œ ์—ฐ
  • Gson์˜ ๋‹จ์ :
    • not Kotlin-friendly
      • ์ฝ”ํ‹€๋ฆฐ์˜ default value ๋ฌด์‹œ
        (primitive ํƒ€์ž… default ๊ฐ’: 0, reference ํƒ€์ž… default ๊ฐ’: null)
      • ์ฝ”ํ‹€๋ฆฐ์˜ null-safety ์ค€์ˆ˜ํ•˜์ง€ X
    • Moshi๋ณด๋‹ค method ์ˆ˜ ๋งŽ์Œ (Gson: ~1036๊ฐœ, Moshi: ~500๊ฐœ)
    • Moshi๋ณด๋‹ค ์šฉ๋Ÿ‰ ํผ (Gson: ~300kB, Moshi: ~120kB)
    • Java reflection ์‚ฌ์šฉ

Moshi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • Moshi ์ง๋ ฌํ™” ๋ฐฉ์‹:
    • ์ฝ”๋“œ ์ƒ์„ฑ ์‚ฌ์šฉ:
      • reflection ๋Œ€์‹  ์ฝ”๋“œ ์ƒ์„ฑ(code-gen) ์‚ฌ์šฉ
      • ์ปดํŒŒ์ผ ์‹œ์ ์— ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๋กœ์ง ์ƒ์„ฑ
    • ์ •์  ํƒ€์ž… ์•ˆ์ •์„ฑ:
      • Kotlin data class์™€ ๊ฐ™์€ ์ •์  ํƒ€์ž… ์•ˆ์ •์„ฑ ์ง€์›
      • ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ฐ์ดํ„ฐ ํƒ€์ž… ํ™•์ธ -> ๋Ÿฐํƒ€์ž„ ์‹œ ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ถˆ์ผ์น˜ ์˜ค๋ฅ˜ ๋ฐฉ์ง€
    • ๋ถˆ๋ณ€(immutable) ๊ฐ์ฒด ์ง€์›
  • Moshi์˜ ์žฅ์ :
    • More human-readable serialization failed messages
    • built-in support for polymorphic datatypes
    • Code-gen adapter for Kotlin

ํ”„๋กœ์ ํŠธ ๋ฆฌํŒฉํ† ๋ง (Gson -> Moshi)

๐Ÿ“Œ์ฐธ๊ณ ์ž๋ฃŒ: Moshi ๊ด€๋ จ Exception error | Inflearn

๋ฆฌํŒฉํ† ๋ง ์ „

  • TourItemJsonConverter.kt
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

object TourItemJsonConverter {
    fun toJson(tourItem: TourItem): String {
        val serializedPair = tourItem.getContentTypeId() to Gson().toJson(tourItem)
        return Gson().toJson(serializedPair)
    }

    fun fromJson(json: String): TourItem {
        val type = object : TypeToken<Pair<String, String>>() {}.type
        val serializedPair: Pair<String, String> = Gson().fromJson(json, type)

        val tourItemType = when (serializedPair.first) {
            TourContentTypeId.TOURIST_DESTINATION.contentTypeId -> {
                object : TypeToken<TourItem.TouristDestination>() {}.type
            }
            TourContentTypeId.CULTURAL_FACILITIES.contentTypeId -> {
                object : TypeToken<TourItem.CulturalFacilities>() {}.type
            }
            TourContentTypeId.RESTAURANT.contentTypeId -> {
                object : TypeToken<TourItem.Restaurant>() {}.type
            }
            TourContentTypeId.LEISURE_SPORTS.contentTypeId -> {
                object : TypeToken<TourItem.LeisureSports>() {}.type
            }
            TourContentTypeId.EVENT_PERFORMANCE_FESTIVAL.contentTypeId -> {
                object : TypeToken<TourItem.EventPerformanceFestival>() {}.type
            }
            else -> {
                object : TypeToken<TourItem.TouristDestination>() {}.type
            }
        }
        return Gson().fromJson(serializedPair.second, tourItemType)
    }
}

๋ฆฌํŒฉํ† ๋ง ํ›„

  • build.gradle(app)์— plugin & dependency ์ถ”๊ฐ€
    • kapt ๋Œ€์‹  ksp plugin์„ ์‚ฌ์šฉํ•ด์•ผ build ์˜ค๋ฅ˜๊ฐ€ ์•ˆ๋‚œ๋‹ค!
plugins {
    id("com.google.devtools.ksp").version("1.6.10-1.0.4")
}

dependencies {
    implementation("com.squareup.moshi:moshi:1.15.0")
    implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
    ksp ("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
    implementation("com.squareup.moshi:moshi-adapters:1.9.2")
}
  • data class์— @JsonClass(generateAdapter = true) ์• ๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€
  • TourItemJsonConverter.kt
    • Polymorphic ํƒ€์ž… ๊ฐ์ฒด๋ฅผ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”ํ•  ๋•Œ PolymorphicJsonAdapterFactory ์‚ฌ์šฉ!
    • ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ์—๋Ÿฌ Moshi.JsonDataException ํƒ€์ž…์œผ๋กœ catchํ•ด stack trace ์ถœ๋ ฅ
import android.util.Log
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory

object TourItemJsonConverter {
    private val TAG = "TourItemJsonConverter"

    private val moshi = Moshi.Builder()
        .add(
            PolymorphicJsonAdapterFactory.of(TourItem::class.java, "tour_item_type")
                .withSubtype(TourItem.TouristDestination::class.java, "tourist_attraction")
                .withSubtype(TourItem.CulturalFacilities::class.java, "cultural_facilities")
                .withSubtype(TourItem.Restaurant::class.java, "restaurant")
                .withSubtype(TourItem.LeisureSports::class.java, "leisure_sports")
                .withSubtype(TourItem.EventPerformanceFestival::class.java, "event_performance_festival")
        )
        .add(KotlinJsonAdapterFactory())
        .build()

    fun toJson(tourItem: TourItem): String {
        Log.d(TAG, "toJson, TourItem:${tourItem.getTitle()}")
        return try{
            moshi.adapter(TourItem::class.java).toJson(tourItem)
        }catch (e:JsonDataException){
            e.printStackTrace()
            ""
        }
    }

    fun fromJson(json: String): TourItem? {
        Log.d(TAG, "fromJson, json: ${json}")
        return try {
            moshi.adapter(TourItem::class.java).fromJson(json)
        }catch (e:JsonDataException){
            e.printStackTrace()
            null
        }
    }
}

0๊ฐœ์˜ ๋Œ“๊ธ€