[안드로이드 클라이언트] CH4 Gson으로 JSON을 다뤄보자

0
post-thumbnail

[안드로이드 클라이언트] CH4 Gson으로 JSON을 다뤄보자

이 포스팅은 <SNS 앱을 만들면서 배우는 안드로이드 클라이언트 개발>, 장성환, 비제이퍼블릭(2022)을 읽고 개인 학습용으로 정리한 글입니다.

4.1 JSON이란

  • JSON(JavaScript Object Notion): 다양한 구조를 표현할 수 있는 데이터 포맷

    • 자바스크립트에서 파생
    • 텍스트로 표현, 구조 단순 -> 대부분의 언어에 JSON을 다루는 라이브러리 존재
    • 미디어 타입: application/json, 확장자: .json
  • JSON에서 사용할 수 있는 자료형:

    • 숫자: 10진수 정수와 실수
    • 문자열: 구분에 큰 따옴표만 사용
    • 불리언: 소문자로 따옴표 없이 true, false
    • null: 따옴표 없이 null
    • 배열: 대괄호로 감싸고 콤마로 구분, 순서 있음, 타입 혼합 가능
    • 오브젝트: 중괄호로 감싸고 콤마로 구분, 키-값 쌍으로 이루어진 요소들(키는 문자열 사용, 키와 값은 콜론(:)으로 구분), 순서 없는 집합
  • 날짜와 메세지를 포함하는 응답을 JSON 포맷으로 만들면: date와 message를 키로 갖는 오브젝트

{
	"date": "2021-04-12T04:54:08Z,
    "message": "Hello, world!"
}

TodayFragment.kt

  • HttpURLConnection의 setRequestProperty메서드로 요청에 헤더 추가 가능
    -> 이 메서드를 사용해 Accept 헤더 추가
    -> "Hello, world!" API는 요청의 Accept 헤더에 따라 text/plain과 application/json 타입의 응답 전송

            conn.setRequestProperty("Accept", "application/json")
  • API 응답으로 받은 문자열을 JSONObject의 생성자로 넘겨 JSON 오브젝트 만들기
  • JSON 오브젝트에서 키 값을 가져올 땐 타입에 맞는 메서드 사용
    -> ex. getString(), getInt(), getObject()
			val json = JSONObject(body)
            val date = json.getString("date")
            val message = json.getString("message")

4.2 Gson 사용

  • JSONObject를 사용하는 것의 단점:
    • API 각각의 응답 구조를 항상 기억하기 어렵다
    • 응답의 구조가 바뀌었을 때 사용하는 부분을 일일이 찾아 수정해야
    • JSONObject에서 지원하는 타입으로만 값을 가져올 수 있음
      -> 다른 타입을 사용하려면 매번 변환하는 작업 필요
  • Gson: JSON 표현을 자바 객체로, 자바 객체를 JSON 표현으로 변환해주는 라이브러리
    • 구글 내부에서 사용하던 것이 오픈소스로 배포된 것
  • Gson 사용하기 위해선 모듈의 build.gradle에 의존성 추가해야
dependencies {
	...
    implementation 'com.google.code.gson:gson:2.8.8'
    ...
}
  • API 응답에 사용할 클래스들을 모아두기 위한 online.dailyq.api.response 패키지 생성
    -> "Hello, world!" API의 응답을 정의한 클래스 생성 = Gson에서 사용할 모델

  • Gson에서 사용할 모델은 API 응답과 같은 구조, 이름, 타입을 가짐

HelloWorld.kt

package online.dailyq.api.response

data class HelloWorld (val date: String, val message: String)

TodayFragment.kt

  • Gson 초기화 -> fromJson()메서드에 JSON과 클래스 넘겨줌
 			val gson = Gson()
            val helloWorld = gson.fromJson(body, HelloWorld::class.java)

            activity?.runOnUiThread {
                binding.date.text = helloWorld.date
                binding.question.text = helloWorld.message
            }

HelloWorld.kt

  • Gson에서는 HelloWorld 모델에서 date 타입을 Date로 변경하기만 하면 자동으로 변환됨
package online.dailyq.api.response

import java.util.Date

data class HelloWorld (val date: Date, val message: String)

TodayFragment.kt

  • Date: 시간을 담기 위한 객체
    -> Date를 원하는 형식의 문자열로 만들기 위해서 DateFormat 사용

  • DateFormat의 getDateInstance(): 스타일과 로케일을 인자로 받아 그에 맞는 DateFormat 객체 생성
    -> 로케일 Locale.KOREA일 때 스타일에 따라 다음의 형식 반환

    • FULL: 2022년 1월 12일 수요일
    • LONG: 2022년 1월 12일 (수)
    • MEDIUM: 2022.1.12.
    • SHORT: 22.1.12.
			val gson = Gson()
            val helloWorld = gson.fromJson(body, HelloWorld::class.java)
            val dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.KOREA)

            activity?.runOnUiThread {
                binding.date.text = dateFormat.format(helloWorld.date)
                binding.question.text = helloWorld.message
            }

4.2.1 JSON 속성 네이밍 정책

  • 일반적으로 코틀린/자바에서는 Camel case 사용
    일반적으로 JSON에서는 Snake case 사용

  • 기본적으로 JSON 속성의 이름과 클래스 속성의 이름이 같은 것으로 매핑
    -> 서로 다른 문자열이 같은 것임을 알리기 위해 Gson 네이밍 정책 지정해야

  • Gson 객체는 생성 후 변경할 수 X
    -> 생성하기 전에 GsonBuilder 사용해 원하는 설정해야

val gson = GsonBuilder()
	.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
    .create()
  • setFieldNamingPolicy()에 사용 가능한 값:
    • IDENTITY: 모델과 JSON 정확히 같은 이름 사용
    • LOWER_CASE_WITH_UNDERSCORES: JSON 필드 소문자로 표기, 단어 연결 언더바(_)
    • LOWER_CASE_WITH_DASHES: JSON 필드 소문자로 표기, 단어 연결 하이픈(-)
    • UPPER_CAMEL_CASE: JSON 필드 대문자로 표기, 단어 연결 변경 X
    • UPPER_CAMEL_CASE_WITH_SPACES: JSON 필드 대문자로 표기, 단어 연결 공백
  • JSON 속성의 이름과 클래스 멤버의 이름이 일정한 규칙을 따르지 않을 때:
    @SerializedName 어노테이션을 사용하여 명시적으로 지정

4.2.2 커스텀 직렬화와 역직렬화

  • Gson에서 JSON을 객체로 변환해야할 때
    -> 기본 타입(ex. String, Int)은 별다른 설정 없이도 변환 O
    -> 사용자가 직접 만든 클래스열거형(enum)변환 작업을 할 어댑터를 GsonBuilder에 등록해야

  • 직렬화는 JsonSerializer 인터페이스를, 역직렬화는 JsonDeserializer 인터페이스를 구현

import com.google.gson.*
import java.lang.reflect.Type

enum class Build{
    PRODUCT, RC, DEV
}
object BuildTypeAdapter : JsonSerializer<Build>, JsonDeserializer<Build> {
    override fun serialize(
        src: Build,
        typeOfSrc: Type?,
        context: JsonSerializationContext?
    ): JsonElement {
        return JsonPrimitive(src.toString())
    }

    override fun deserialize(
        json: JsonElement,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): Build {
        return Build.valueOf(json.asString)
    }
}
val gson = GsonBuilder()
	.registerTypeAdapter(Build::class.java, BuildTypeAdpater)
    .create()
  • TypeAdapter 등록 후 모델에서 Build 타입 사용 가능

4.2.3 null값 직렬화

  • Gson의 기본 설정은 객체의 null인 멤버를 직렬화에서 생략

  • null인 멤버로 JSON 문자열로 직렬화해야할 때
    -> GsonBuilder의 serializeNulls() 메서드

4.2.4 보기 좋게 출력하기

  • Gson은 효율성을 위해 JSON 문자열 개행/공백 생략

  • 들여쓰기/개행 등 사람이 읽기 좋게 만들어야할 때
    -> GonBuilder의 setPrettyPrinting() 메서드

profile
Be able to be vulnerable, in search of truth

0개의 댓글