공부/프로그래밍

[retrofit, gson] Expected BEGIN_OBJECT but was STRING 에러 해결(String -> LocalDate)

demonic_ 2021. 1. 18. 08:00

Retrofit을 사용하면서 위의 에러가 발생했는데, 확인해보니 String 을 특정 타입으로 변경할때 발생한 에러다.

정확히는 Retrofit이 아닌 Gson 에서 변환하던 중 에러가 발생한 것이다. 샘플을 통해 알아보자.

 

우선 기존 코드는 다음과 같다

@Getter
@ToString
public class ResponseApiTest {
    @SerializedName("date")
    private LocalDate date;

    @SerializedName("time")
    private LocalTime time;

    @SerializedName("datetime")
    private LocalDateTime datetime;
}

다음 에러가 발생했다.

2021-01-17 15:56:15.827  INFO 18290 --- [           main] okhttp3.OkHttpClient                     : {"date":"2021-01-17","time":"16:12:56","datetime":"2021-01-17T16:12:56.421271"}
2021-01-17 16:13:43.962  INFO 18840 --- [           main] okhttp3.OkHttpClient                     : <-- END HTTP (176-byte body)

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 10 path $.date

 

데이터는 제대로 받아오는거 같은데, 처음엔 의아했다. 그러다 GJON으로 변경하는 과정에서 문제가 발생한걸 알았다.

 

예를들어 다음과 같이 수정하면 에러가 발생하지 않는다.

@Getter
@ToString
public class ResponseApiTest {
    @SerializedName("date")
    private String date;

    @SerializedName("time")
    private String time;

    @SerializedName("datetime")
    private String datetime;
}
2021-01-17 16:15:08.041  INFO 18886 --- [           main] okhttp3.OkHttpClient                     : {"date":"2021-01-17","time":"16:15:08","datetime":"2021-01-17T16:15:08.03601"}
2021-01-17 16:15:08.041  INFO 18886 --- [           main] okhttp3.OkHttpClient                     : <-- END HTTP (175-byte body)
response.body() = ResponseApiTest(date=2021-01-17, time=16:15:08, datetime=2021-01-17T16:15:08.03601)

 

 

GSON에서 String을 LocalDate 유형으로 변경하면서 에러가 발생했다.

그래서 이전의 LocalDate 유형을 사용하기 위해서는 GSON의 converter를 등록하는 것을 수정해야 한다.

 

일전에 Retrofit의 설정은 다음과 같았다.

...
    @Bean
    public KinsConnectorApi kinsConnectorApi() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(host + ":" + port)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        return retrofit.create(KinsConnectorApi.class);
    }
...

 

위 설정에서 addConverterFactory에 String을 LocalDate 형식으로 파싱하는 것을 추가해야 한다.

 

설정에 다음을 추가한다.

...
    @Bean
    public KinsConnectorApi kinsConnectorApi() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(host + ":" + port)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        return retrofit.create(KinsConnectorApi.class);
    }

    // String을 LocalDate, LocalTime, LocalDateTime 으로 변형하는걸 등록한다.
    private GsonConverterFactory gsonConverterFactory() {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
                    @Override
                    public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
                    }
                })
                .registerTypeAdapter(LocalDate.class, new JsonDeserializer<LocalDate>() {
                    @Override
                    public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalDate.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    }
                })
                .registerTypeAdapter(LocalTime.class, new JsonDeserializer<LocalTime>() {
                    @Override
                    public LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("HH:mm:ss"));
                    }
                })
                .create();

        return GsonConverterFactory.create(gson);
    }
...

 

 

Response의 Type을 이전처럼 LocalDate로 변경한다

@Getter
@ToString
public class ResponseApiTest {
    @SerializedName("date")
    private LocalDate date;

    @SerializedName("time")
    private LocalTime time;

    @SerializedName("datetime")
    private LocalDateTime datetime;
}

다시 실행해보자.

2021-01-17 16:22:03.742  INFO 19061 --- [           main] okhttp3.OkHttpClient                     : {"date":"2021-01-17","time":"16:22:03","datetime":"2021-01-17T16:22:03.736916"}
2021-01-17 16:22:03.742  INFO 19061 --- [           main] okhttp3.OkHttpClient                     : <-- END HTTP (176-byte body)
response.body() = ResponseApiTest(date=2021-01-17, time=16:22:03, datetime=2021-01-17T16:22:03.736916)

 

끝.