Retrofit으로 XML 파싱하기 + 삽질

장꾸꾸·2022년 2월 15일
post-thumbnail

기존에 만들어두었던 토이 프로젝트의 공공 API가 더 이상 서비스되지 않아 API를 교체했습니다.

기존에 사용했던 코로나 관련 API의 경우 JSON으로 응답을 내려줬지만, 교체하게 된 API의 경우 XML로만 내려주더라구요.

Retrofit 더하기 XML

Tikxml

SimpleXmlConverter 는 deprecated 되었고, JAXB(Jakarta XML Binding) converter 는 안드로이드를 지원하지 않아서~Tikxml를 사용했습니다.

어노테이션이 생소해서 그렇지 사용하기 편리한 라이브러리입니다. 기존에 retrofit으로 JSON 파싱하는 것과 거의 동일합니다. 그냥 GSON 대신 XML로 컨버팅해준다라고 생각하면 됩니다.

종속성 추가

    //xml parser
    implementation 'com.tickaroo.tikxml:annotation:0.8.13'
    implementation 'com.tickaroo.tikxml:core:0.8.13'
    implementation 'com.tickaroo.tikxml:retrofit-converter:0.8.13'
    kapt 'com.tickaroo.tikxml:processor:0.8.13'

converter

object RetrofitClient {
    fun getXMLInstance() : Retrofit{
        
        val parser = TikXml.Builder().exceptionOnUnreadXml(false).build()
            
        return Retrofit.Builder()
                .baseUrl(Constants.BASE_URL)
                .addConverterFactory(TikXmlConverterFactory.create(parser))
                .build()
    }
}
  • TikXml.Builder().exceptionOnUnreadXml(false).build() 는 원하지 않는 데이터는 제외하기 위해서입니다. 내려받은 데이터를 모두 사용하신다면 추가하지 않아도 됩니다.

data class 만들기

여기서부터 조금 생소하게 느껴질 수 있습니다. JSON 넣으면 data class 바로 뽑아주는 친절한 코틀린에 익숙해져버린 저는 버벅였지만 여러분은 절대 어렵게 느끼지 않을 거예요.

<response>
<header>
<resultCode>00</resultCode>
<resultMsg>NORMAL SERVICE.</resultMsg>
</header>
<body>
<items>
<item>
<addr>부산광역시 해운대구 좌동순환로 173 502호 (좌동, 영풍프라자)</addr>
<mgtStaDd>20220215</mgtStaDd>
<pcrPsblYn>N</pcrPsblYn>
<ratPsblYn>Y</ratPsblYn>
<recuClCd>31</recuClCd>
<sgguCdNm>부산해운대구</sgguCdNm>
<sidoCdNm>부산</sidoCdNm>
<XPos>995224</XPos>
<XPosWgs84>129.1752749</XPosWgs84>
<YPos>471795</YPos>
<YPosWgs84>35.1781714</YPosWgs84>
<yadmNm>정내과의원</yadmNm>
<ykihoEnc>JDQ4MTAxMiM1MSMkMiMkMCMkMDAkMzgxMTkxIzUxIyQxIyQxIyQ4MiQyNjEwMDIjNzEjJDEjJDgjJDgz</ykihoEnc>
</item>
</items>
<numOfRows>1</numOfRows>
<pageNo>1</pageNo>
<totalCount>4469</totalCount>
</body>
</response>

위와 같은 형식으로 내려올 경우 다음과 같이 POJO를 작성해줍니다.
Item의 경우 사용할 데이터만 작성했습니다.

@Xml(name = "response")
data class Hospital(
    @Element(name = "body")
    val body: Body,
    @Element(name="header")
    val header: Header
)

@Xml(name="header")
data class Header(
    @PropertyElement(name="resultCode")
    val resultCode: Int,
    @PropertyElement(name="resultMsg")
    val resultMsg: String
)

@Xml(name = "body")
data class Body(
    @Element(name="items")
    val items: Items,
    @PropertyElement(name="numOfRows")
    val numOfRows: Int,
    @PropertyElement(name="pageNo")
    val pageNo: Int,
    @PropertyElement(name="totalCount")
    val totalCount: Int
)

@Xml(name= "items")
data class Items(
    @Element(name="item")
    val item: List<Item>
)


@Xml
data class Item(
    @PropertyElement(name = "addr")
    var addr: String,
    @PropertyElement(name = "sidoCdNm")
    var sidoCdNm: String,
    @PropertyElement(name="yadmNm")
    var yadmNm: String,
)

자세한 사용법은 아래 tikxml 공식 문서에 설명되어 있습니다.

https://github.com/Tickaroo/tikxml/blob/master/docs/AnnotatingModelClasses.md

간단히 살펴보면

@PropertyElementnest child element가 없는 경우
@Elementnest child element를 가진 경우

그러니까, 딸린 자식이 있으면 @Element
싱글이면 @PropertyElement

(name="yadmNm") 공식 문서에 나와있듯 optional입니다.
이게 전부입니다. 나머지는 retrofit 쓰시던 그대로 하시면 됩니다.

삽질

Could not locate ResponseBody converter...

저를 삽질하게 한 오류입니다. 이틀을 날렸어요 만약 이 오류를 보신다면 아래 사항들을 확인해보세요.

  1. Tikxml 라이브러리를 제대로 추가해주었는지
    • 말 그대로 retrofit이 응답을 이해하게 해줄 컨버터를 잘 연결해줬는지
  1. data class 즉, POJO를 제대로 작성했는지

    • 아래는 stackoverflow에서 가져온 답변입니다. JSON과 달리 XML은 역직렬화시 추상 클래스를 사용할 수 없습니다. 따라서 구상 클래스(concrete type)으로 작성해주어야 합니다.

      XML can only be deserialized to a concrete type and not a list of types like JSON. In this case, the body type should represent the tag and its children and not List.

  2. 마지막으로, 통신이 제대로 가고 있는지,,,


    - 제가 이랬습니다. 평소에 Interceptor 꼭꼭 달아주는 습관을 들이세요. 왜냐면 정신건강에 좋거든요. 로그만 제대로 찍어봤어도 제가 잘못된 인증키를 사용하고 있다는 걸 금방 알 수 있었을 겁니다.

    포털에서 제공되는 Encoding/Decoding 된 인증키를 적용하면서 구동되는 키를 사용하시기 바랍니다. 이런 말은 절대 넘겨짚지 마세요 🥺

요즘은 대부분 JSON을 사용하지만 간혹 XML을 파싱해야할 일이 생길수도(?) 있으니까요. 혹시라도 생겨서 들어오셨다면 이럇시아리마세! tikxml 사용해보시는 것도 좋을 거 같습니다. 특히 공공기관 API는 XML을 내려주는 경우가 종종 있습니다.

참고자료
https://medium.com/@sameerzinzuwadia/android-kotlin-xml-parsing-with-retrofit-6879401d7901
https://developer-eungb.tistory.com/24

profile
사료값 벌러 나온 안드로이드 개발자

0개의 댓글