버튼 수집상

[안드로이드] 가변적인 json 키를 동일한 클래스로 파싱하기 본문

TIL - 안드로이드

[안드로이드] 가변적인 json 키를 동일한 클래스로 파싱하기

cocokaribou 2023. 4. 18. 15:31

배경

같은 종류의 데이터가 각기 다른 json 키 이름으로 들어오는 api 가 있었다.

구글의 gson 라이브러리는 json 키와 데이터 클래스 변수명을 1:1로 매핑시키므로 별도의 처리가 필요했다.

예시

데이터 클래스

data class TestResponse(
    // 키 이름이 다른 json 값들을 읽어서 List에 add.
    val areaList: List<AreaData>?
) {
    data class AreaData(
        @SerializedName("itemType") val itemType: String?,
        @SerializedName("imagePath") val imagePath: String?
    )
}

응답값 json

{
  "banner1" : {
    "itemType" : "banner1",
    "imagePath" : "banner1"
  },
  "banner2" : {
    "itemType" : "banner2",
    "imagePath" : "banner2"
  },
  "tab1" : {
    "itemType" : "tab1",
    "imagePath" : "tab1"
  },
  "goods1" : {
    "itemType" : "goods1",
    "imagePath" : "goods1"
  },
  "goods2" : {
    "itemType" : "goods2",
    "imagePath" : "goods2"
  },
  "tab2" : {
    "itemType" : "tab2",
    "imagePath" : "tab2"
  }
}

방법

먼저 커스텀 TypeAdapter 를 만들어준다

class AreaDataTypeAdapter : TypeAdapter<TestResponse>() {

    override fun write(out: com.google.gson.stream.JsonWriter?, value: TestResponse?) {
        // not needed in this case
    }

    override fun read(reader: com.google.gson.stream.JsonReader?): TestResponse {
        val list = mutableListOf<TestResponse.AreaData>()
        var itemType: String? = null
        var imgPath: String? = null

        reader?.let {
            reader.beginObject()
            while (reader.hasNext()) {
                val key = reader.nextName()
                // json 키 패턴을 regex로 정의
                val pattern = Regex("(banner|sns|goods|gallery|tab)\\d+")

                if (pattern.matches(key)) {
                    reader.beginObject()
                    while (reader.hasNext()) {
                        val innerKey = reader.nextName()
                        when (innerKey) {
                            "itemType" -> itemType = reader.nextString()
                            "imagePath" -> imgPath = reader.nextString()
                            else -> reader.skipValue()
                        }
                    }
                    reader.endObject()
                    list.add(TestResponse.AreaData(itemType, imgPath))
                } else {
                    reader.skipValue()
                }
            }
            reader.endObject()
        }
        return TestResponse(list)
    }
}

만든 TypeAdapter 를 register 해준다.

 

1) retrofit에 붙이기

val gson = GsonBuilder()
    .registerTypeAdapter(TestResponse::class.java, AreaDataTypeAdapter())
    .create()

return Retrofit.Builder()
    .baseUrl(BASE_DOMAIN)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .client(client)
    .build()
    .create(ApiService::class.java)

2) json 로컬데이터를 쓸 때

val jsonString = getJsonFileToString("jsons/test.json", BaseApp.context)
val gson = GsonBuilder()
    .registerTypeAdapter(TestResponse::class.java, AreaDataTypeAdapter())
    .create()
val data = gson.fromJson(jsonString, TestResponse::class.java)

 

빌드해보고 에러가 난다면
json 키와 데이터 타입을 맞게 처리했는지 확인.

list 나 클래스처럼 뎁스가 깊어지면 그에 대한 처리도 필요.

참고

 

Android - Convert json which has uncertain keys to map using kotlin

I have a json like this. I need to convert it to data class { "0": { "id": "111", "type": "1", "items": [ ...

stackoverflow.com

 

728x90