World Time Api를 호출해서 Response에 들어있는 datetime 값을 가져와야 했다.
datetime 값만 따로 뽑아올 수 있었지만 Api Response를 Class로 받아오고 싶었다.
그래서 Response 값을 전부 받을 수 있는 WorldClockApiResponse class를 만들었다.
data class WorldClockApiResponse(
val abbreviation: String,
val clientIp: String,
val datetime: LocalDateTime,
val dayOfWeek: Int,
val dayOfYear: Int,
val dst: Boolean,
val dstFrom: String?,
val dstOffset: Int,
val dstUntil: String?,
val rawOffset: Int,
val timezone: String,
val unixtime: Long,
val utcDatetime: LocalDateTime,
val utcOffset: String,
val weekNumber: Int
)
Response를 WorldClockApiResponse class로 파싱하기 위해
val worldClockApiResponse = Gson().fromJson(httpResponse, WorldClockApiResponse::class.java)
를 실행했는데 다음과 같은 에러가 발생했다.
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 65 path $.datetime
원인은 WorldClockApiResponse의 LocalDateTime에 있었다.
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = this.fromJson((String)json, (Type)classOfT);
return Primitives.wrap(classOfT).cast(object);
}
Gson의 fromJson()은 json 문자열 Primitive 타입으로 형변환하여 class에 저장하는데
LocalDateTime는 Primitive 타입이 아니기 때문에 문제가 발생한 것이었다.
val gson = GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(
LocalDateTime::class.java,
JsonDeserializer { json, _, _ ->
LocalDateTime.parse(json.asString, DateTimeFormatter.ISO_DATE_TIME)
}).create()
문제를 해결하기 위해 GsonBuilder()를 사용했다.
GsonBuilder로 json 문자열을 파싱할 때 LocalDateTime 타입을 인식하게 했고
Response가 snack_casing으로 되어있었기 때문에 Kotlin의 naming convention인 camelCasing으로 변환하기 위해
FieldNameingPolicy를 LOWER_CASE_WITH_UNDERSCORES로 설정했다.
그 결과, WorldClockApiResponse를 이쁘게 받아올 수 있었다.