공공 데이터 포털 사이트에서 Open API를 활용하여 다양한 서비스를 만들 수 있습니다. 이번에 지도 서비스에 경기도 공중화장실 현황 데이터를 연동시켜 사용자들에게 현 위치에 대한 화장실 위치와 정보를 제공할려고 하는 서비스를 만들어보고자 개발하고 있습니다.
경기도 공중화장실 현황 데이터 : https://www.data.go.kr/data/15011425/openapi.do
윗 Open API 에서는 아래와 같은 데이터를 응답값으로 전달해줍니다.
사진으로 보다시피 응답값은 XML
타입으로 내려주고 있습니다. 이를 클라이언트에서 필요한 데이터를 파싱하여 사용하기 위해 JSON
타입으로 변환을 해보았습니다.
사진으로 보다시피 차이는 전혀 없습니다. 그래서 XML
타입의 응답값을 JSON
타입으로 변환할 수 있는 방법을 찾아보다가 TikXML
이란 라이브러리를 알게 되었습니다.
TikXML
라이브러리는 Android 및 Java 애플리케이션에서 XML을 파싱하기 위한 라이브러리이며, Kotlin에서도 사용할 수 있습니다. TikXML은 안드로이드에서 XML을 파싱하는 간단하고 효율적인 방법을 제공하여 XML 데이터를 쉽게 읽고 처리할 수 있도록 합니다.
TikXML 라이브러리 링크: https://github.com/Tickaroo/tikxml
TikXML의 주요 특징은 다음과 같습니다
- 간편한 구성: TikXML은 단순하고 직관적인 구성을 통해 XML을 파싱할 수 있습니다. 사용자가 XML 데이터를 모델 클래스로 변환하기 위해 복잡한 구성을 하지 않아도 됩니다.
- Annotation 기반 매핑: TikXML은 Java 및 Kotlin 클래스를 XML 요소와 매핑하기 위해 Annotation을 사용합니다. 이를 통해 클래스 및 필드의 이름을 XML 요소와 일치시킬 수 있으며, 필요한 경우 XML 구조와 클래스 구조 간의 매핑을 세밀하게 제어할 수 있습니다.
- 커스텀 타입 지원: TikXML은 기본적인 데이터 타입 외에도 커스텀 데이터 타입을 지원합니다. 따라서 복잡한 XML 구조를 가진 데이터도 쉽게 파싱할 수 있습니다.
- 성능 최적화: TikXML은 XML을 효율적으로 파싱하여 성능을 최적화합니다. 속도가 빠르고 메모리 사용량이 적으며, 대량의 XML 데이터도 효율적으로 처리할 수 있습니다.
- Android 및 Java 호환성: TikXML은 Android 및 Java 애플리케이션 모두에서 사용할 수 있습니다. Android 애플리케이션의 경우 TikXML은 Android SDK와 호환되며, Android 앱에서 XML 데이터를 처리하는 데 특히 유용합니다.
build.gradle
파일에 아래 디펜던시를 추가해줍니다. // TikXML
val tikxml_version = "0.8.13"
implementation("com.tickaroo.tikxml:annotation:$tikxml_version")
implementation("com.tickaroo.tikxml:core:$tikxml_version")
implementation("com.tickaroo.tikxml:retrofit-converter:$tikxml_version")
kapt("com.tickaroo.tikxml:processor:$tikxml_version")
Retrofit
의존성을 수정해줍니다.@Module
@InstallIn(SingletonComponent::class)
object AppModule {
// Base URL
private const val baseUrl = "https://openapi.gg.go.kr/"
// Api Key
private const val apiKey = "172e4fdad76d4b6aa8f34ad805d9dfb3"
// OkhttpClient
@Singleton
@Provides
fun provideOkhttpClient(): OkHttpClient {
val httpLoggingInterceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BASIC)
return OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
}
// Retrofit
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
val parser = TikXml.Builder().exceptionOnUnreadXml(false).build() <-- [XML -> JSON 변환을 위한 parser 변수 선언, 필요 없는 항목의 데이터를 제외하고 받고 싶을 때 사용]
return Retrofit.Builder()
.addConverterFactory(TikXmlConverterFactory.create(parser)) <-- [기존 GsonConverterFactory.create() 대신 TikXmlConverterFactory.create() 사용, create() 메소드의 파라미터로 parser 변수 연결
.client(okHttpClient)
.baseUrl(baseUrl)
.build()
}
// Toilet Api Service
@Singleton
@Provides
fun provideToiletService(retrofit: Retrofit): ToiletService {
return retrofit.create(ToiletService::class.java)
}
}
data class
를 생성합니다.@Xml(name = "Publtolt")
data class FetchToiletResponse(
@Element(name = "row")
val row: List<ToiletData>,
@Element(name = "head")
val head: Head
)
@Xml(name = "head")
data class Head(
@PropertyElement(name = "list_total_count")
val listTotalCount: String,
@PropertyElement(name = "api_version")
val api_version: String,
@Element(name = "RESULT")
val result: Result
)
@Xml(name="RESULT")
data class Result(
@PropertyElement(name = "CODE")
val code: String,
@PropertyElement(name = "MESSAGE")
val message: String,
)
@Xml(name="row")
data class ToiletData(
// 화장실 명
@PropertyElement(name = "PBCTLT_PLC_NM")
val PBCTLT_PLC_NM: String? = null,
// 소재지 도로명 주소
@PropertyElement(name = "REFINE_ROADNM_ADDR")
val REFINE_ROADNM_ADDR: String? = null,
// 소재지 지번 주소
@PropertyElement(name = "REFINE_LOTNO_ADDR")
val REFINE_LOTNO_ADDR: String? = null,
// 남녀 공용 화장실 여부
@PropertyElement(name = "MALE_FEMALE_CMNUSE_TOILET_YN")
val MALE_FEMALE_CMNUSE_TOILET_YN: String? = null,
// 남성용 대변기 수
@PropertyElement(name = "MALE_WTRCLS_CNT")
val MALE_WTRCLS_CNT: String? = null,
// 남성용 소변기 수
@PropertyElement(name = "MALE_UIL_CNT")
val MALE_UIL_CNT: String? = null,
// 남성용 장애인용 대변기 수
@PropertyElement(name = "MALE_DSPSN_WTRCLS_CNT")
val MALE_DSPSN_WTRCLS_CNT: String? = null,
// 남성용 장애인용 소변기 수
@PropertyElement(name = "MALE_DSPSN_UIL_CNT")
val MALE_DSPSN_UIL_CNT: String? = null,
// 남성용 어린이용 대변기 수
@PropertyElement(name = "MALE_KID_WTRCLS_CNT")
val MALE_KID_WTRCLS_CNT: String? = null,
// 남성용 어린이용 소변기 수
@PropertyElement(name = "MALE_KID_UIL_CNT")
val MALE_KID_UIL_CNT: String? = null,
// 여성용 대변기 수
@PropertyElement(name = "FEMALE_WTRCLS_CNT")
val FEMALE_WTRCLS_CNT: String? = null,
// 여성용 장애인용 대변기 수
@PropertyElement(name = "FEMALE_DSPSN_WTRCLS_CNT")
val FEMALE_DSPSN_WTRCLS_CNT: String? = null,
// 여성용 어린이용 대변기 수
@PropertyElement(name = "FEMALE_KID_WTRCLS_CNT")
val FEMALE_KID_WTRCLS_CNT: String? = null,
// 관리 기관명
@PropertyElement(name = "MANAGE_INST_NM")
val MANAGE_INST_NM: String? = null,
// 전화 번호
@PropertyElement(name = "MNGINST_TELNO")
val MNGINST_TELNO: String? = null,
// 위도
@PropertyElement(name = "REFINE_WGS84_LAT")
val REFINE_WGS84_LAT: String? = null,
// 경도
@PropertyElement(name = "REFINE_WGS84_LOGT")
val REFINE_WGS84_LOGT: String? = null,
// 화장실 소유 구분
@PropertyElement(name = "TOILET_POSESN_DIV")
val TOILET_POSESN_DIV: String? = null,
)
XML
타입의 데이터를 JSON
타입으로 변환하여 응답값을 받기 위한 data class
를 생성하는데 사용되는 어노테이션은 크게 2가지 있습니다.
자식 여부에 따라
- 자식이 있다(= 태그 사이에 값이 존재한다) ->
@PropertyElement
- 자식이 없다(= 태그 사이에 값이 존재하지 않는다) ->
@Element
TikXML
라이브러리를 사용하기 위한 설정이 모두 마치고 데이터를 요청하면 아래와 같이 JSON
타입으로 파싱되어 데이터가 잘 받아지는걸 확인해볼수 있습니다. 😊