본문 바로가기

Android/Basic

Moshi Converter

최신화: 2023.10.23


‣ Intro: Moshi?

데이터를 다른 종점으로 전송하기 전, 통신이 가능하면서 나중에 재구성이 가능한 포맷으로 변환해줘야한다.
이런 포맷 변환을 직렬화라하며, 변환 포맷중 가장 많이 사용되는 것이 JSON이다.

반대로, 직렬화된 파일을 다시 객체 형태로 변환하는 과정을 역직렬화라 한다.

Moshi는 Square에서 만든 라이브러리로,  JSON과 객체 사이의 직렬화(Serialization) / 역직열화(Deserialization) 쉽고 안전하게 작업할 수 있도록 서포팅하는 라이브러리로, 리플랙션과 Codegen 방식의 변환을 모두 지원한다.

또한, Gson이 가지는 일부 한계점과 문제점을 개선하려는 목적으로 만들어진 만큼, 다음과 같은 장점들을 가진다.

  • 성능
    • Moshi는 성능 최적화에 중점을 둔다.
    • 특히 스트리밍 API를 사용하여 큰 JSON 데이터도 효율적으로 처리할 수 있다.
  • Kotlin 지원:
    • Moshi는 Kotlin과의 통합을 더 잘 지원한다.
    • moshi-kotlin 아티팩트를 사용하면 Kotlin의 특성을 더 잘 활용할 수 있다.
    • 예를 들어, 기본값과 non-null 타입을 올바르게 처리할 수 있다.
  • 코드 생성:
    • Moshi는 런타임 리플렉션 대신 코드 생성(Codegen)을 지원한다.
    • 이는 더 빠른 직렬화 및 역직렬화 속도를 제공하며, 런타임 중 추가 오버헤드를 줄인다.
  • 확장성:
    • Moshi의 API는 확장 및 사용자 정의가 쉽다.
    • 사용자 정의 타입 어댑터나 JSON 어댑터 팩토리를 쉽게 추가할 수 있다.
  • 오류 메시지:
    • Moshi는 오류가 발생했을 때 더 명확하고 유용한 오류 메시지를 제공한다.
    • 이는 문제를 더 쉽게 진단하고 해결할 수 있도록 도와준다.
  • 스트릭트 모드:
    • Moshi는 기본적으로 스트릭트 모드를 사용
    • 이는 잘못된 JSON 형식이나 예상치 않은 필드 값에 대해 더 엄격하게 검사한다.
    • 이러한 스트릭트 검사는 버그나 오류를 미리 발견하는 데 도움이 된다.
  • 모던 API:
    • Moshi는 최신 Java와 Kotlin 기능을 최대한 활용하려고 설계되었다.
    • 그래서 API가 더 간결하고 직관적

‣ Moshi 어노테이션에 JSON 구성 요소 대응시키기

Moshi 함께 사용되는 @Json 어노테이션을 통해 JSON 필드 이름과 Kotlin 프로퍼티 이름 간의 매핑을 정의할 수 있다.

Moshi에서 주로 사용되는 어노테이션들은 다음과 같다.

          1. @JsonClass(generateAdapter = true)
            • 어노테이션은 데이터 클래스에 적용되며, Moshi 코드 생성기를 사용하여 해당 클래스에 대한 JSON 어댑터를 자동으로 생성하도록 지시하며, 해당 어댑터는 JSON 문자열을 해당 클래스의 인스턴스로 변환하거나 반대의 작업을 수행하는 사용된다.
            • @JsonClass JSON Object 대응되는 역할을 하며, JSON Object 대응되는 class 만들 위에 붙인다.
            • 파라미터로 generateAdapter* 있으며, true 설정해 주어야 codegen 방식으로 직렬화, 역직렬화가 가능하다.
          2. @field:Json(name=”[JSON키]”)
            • 어노테이션은 클래스의 프로퍼티에 적용되며, 해당 프로퍼티와 JSON 필드 간의 매핑을 정의한다.
            • @field: Kotlin 프로퍼티에 어노테이션을 적용하기 위한 지정자이다.
            • @field:Json JSON 내부의 Key-Value값에 붙이며, name 파라미터는 JSON 포멧의 key 값에 대응되도록 만든다. 이를 통해 필드에 대한 직렬화/ 역직렬화 동작을 지정할  있다.
            • 필드에 대한 serializeNulls 속성을 사용하여 null값을 직렬화 지에 대한 여부를 정하거나, 필드에 대한 custom adapter 사용하여 Moshi 해당 필드를 어떻게 처리해야 하는지 지정할 있다.
          3. @Json(name=”[JSON키]”)
            • 어노테이션은 클래스의 프로퍼티나 생성자 매개변수에 적용된다.
            • 이것 역시 해당 프로퍼티나 매개변수와 JSON 필드 간의 매핑을 정의하는데, 어노테이션은 프로퍼티에 직접 적용하려면 @field: 지정자를 사용해야 한.

여기서 주의할 점@field:Json(name=”[JSON키]”)과 @Json(name=”[JSON키]”)의 적용 대상이 다르다는 것이다.

보통은 아래와 같이 Data Class에 @Json 어노테이션을 적용하여 사용하기 때문에 구분에 신경쓰는 경우는 많지 않은 편이다.(필자 기준)

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Person(
    @Json(name = "full_name") 
    val fullName: String,
    @Json(name = "age") 
    val age: Int,
    @Json(name = "email_address") 
    val emailAddress: String
)

하지만 일반 Class에서는 생성자 매개변수와 프로퍼티가 명확히 구분될 수 있으므로 알아두는 것이 좋다.

[일반 Class]

class Person(name: String, age: Int) {
    val fullName: String = name
    val personAge: Int = age
}

-----------------------------------------

[Moshi 어노테이션을 적용한 Class]

class Person(@Json(name = "name") name: String, age: Int) {
    @field:Json(name = "full_name")
    val fullName: String = name
    
    @field:Json(name = "person_age")
    val personAge: Int = age
}

위의 예제에서 Person 클래스의 name와 age는 생성자 매개변수이다. 이들은 단순히 생성자에 값을 전달하는 역할만 한다.

반면, fullName과 personAge는 클래스의 프로퍼티로, 클래스 내부에 값을 저장한다.

이런 차이점을 바탕으로 어노테이션들의 차이를 이해하면 좀더 도움이 되리라 생각한다

 


‣ How to Use?

1. 의존성 추가

Moshi Gradle 의존성을 앱 레밸 모듈 "build.gradle (Module:app)"에 추가한다.

implementation 'com.squareup.moshi:moshi:1.14.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.14.0'

상기 버전은 글의 리뉴얼 일자 기준 최신 버전이다.

https://github.com/square/moshi 해당 링크에서 최신 릴리즈 버전을 확인 할 수 있다.

 

2. 데이터 클래스 정의

Json으로 직렬화/역직렬화 하는데 사용할 데이터 클래스를 정의한다.

data class Person(
    val name: String,
    val age: Int
)

 

3. Moshi 인스턴스 생성

val moshi = Moshi.Builder()
                   .add(KotlinJsonAdapterFactory())
                   .build()

 

4. Json을 객체로 변환(fromJson)

val json = """{"name": "John", "age": 30}"""
val adapter: JsonAdapter<Person> = moshi.adapter(Person::class.java)
val person = adapter.fromJson(json)

 

5. 객체를 Json으로 변환(toJson)

val person = Person(name = "John", age = 30)
val adapter: JsonAdapter<Person> = moshi.adapter(Person::class.java)
val json = adapter.toJson(person)

‣ 주의사항

  • Moshi는 기본적으로 non-null 프로퍼티를 필요로 한다. 따라서 nullable 프로퍼티를 사용하려면 타입에 ?를 추가해야 한다.
  • 기본적으로 Moshi 모든 필드 이름을 그대로 사용한다. JSON 이름이 다른 경우, @Json 어노테이션을 사용하여 매핑을 정의할 있다.
data class Person(
    @Json(name = "full_name") 
    val name: String,
    
    val age: Int
)

 


‣ Outro

실습을 통해 사용 방법을 직접 익혀보고 싶다면 Codelabs의 "Get data from the Internet" 을 참고바란다.