마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

안드로이드 retrofit으로 Json 받아올 때 오류...

0 추천

안녕하세요

firebase Realtime DB를 활용한 안드로이드 앱을 공부하고 있습니다.

Retrofit을 사용해서 db를 받아올 때...
"Expected BEGIN_ARRAY but was STRING at line 2 column 1 path$"의 오류가 뜨는데

이틀을 검색해도 해결방법을 모르겠어서 질문드립니다.. 제발 도와주십시오...ㅠㅠㅠㅠ

 

위 서버에서 특정 값을 가져오는 처리를 진행하면

[
  {
    "name": "테스트0501-45",
    "type": "default"
  },
  {
    "name": "테스트0501-3",
    "type": "default"
  },
  {
    "name": "thelatest saved one",
    "type": "default"
  },
  {
    "name": "테스트45",
    "type": "default"
  },
  {
    "name": "dupleCheck23",
    "type": "default"
  },
  {
    "name": "ffff",
    "type": "default"
  },
  {
    "name": "두",
    "type": "default"
  }
]

위와 같은 형태의 Json 형식의 데이터를 안드로이드로 보내줍니다.

 API interface 입니다.

  @GET("/loadCustomers")
    fun loadCustomers(
        @Query("type") type: String,
        @Query("keyword") keyword: String,
        @Query("type2") type2: String,
        @Query("keyword2") keyword2: String,
    ): Call<ArrayList<loadCustomersResponse>>

 

Response class입니다.

@Keep
data class loadCustomersResponse (
    val testArray : ArrayList<Customer>
)

data class Customer(
    val key : String,
    val name: String,
    val type: String
)

 

Api클래스의 메서드 입니다.

class Api {
    companion object {
        val client = OkHttpClient.Builder()
            .connectTimeout(1, TimeUnit.MINUTES)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build()
        val gson = GsonBuilder().setLenient().create()

        val retrofit = Retrofit.Builder()
            .baseUrl("https://us-central1-today-s-eyebrow-kt-ver.cloudfunctions.net/")
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(client)
            .build()
    }


fun loadCustomers(
        type: String, keyword: String,
        type2: String, keyword2: String, callback: (Boolean, Any?) -> Unit,
    ) {
        retrofit.create(FirebaseApi::class.java).loadCustomers(type, keyword, type2, keyword2)
            .enqueue(object : Callback<ArrayList<loadCustomersResponse>> {
                override fun onResponse(
                    call: Call<ArrayList<loadCustomersResponse>>,
                    response: Response<ArrayList<loadCustomersResponse>>,
                ) {
                    callback(true, response.body())
                }

                override fun onFailure(call: Call<ArrayList<loadCustomersResponse>>, t: Throwable) {
                    callback(false, t.message)
                }

            })
    }


}

 

 

선생님들 장문의 질문을 읽어주셔서 감사합니다.

저의 구글링의 미숙함도 있겠지만...

제 선에서는 최선을 다했음에도 답을 못구했기에

먼저 길을 걸어가신 선생님들의 지혜를 구합니다..

 

감사합니다.

안드로이드촙오자 (340 포인트) 님이 2021년 6월 26일 질문

1개의 답변

+1 추천
 
채택된 답변

님이 정의하신 Response class 대로라면 응답 JSON은 다음과 같은 포맷이 되어야 합니다.

@Keep
data class loadCustomersResponse (
    val testArray : ArrayList<Customer>
)
 

{
"testArray": [
  {
    "name": "테스트0501-45",
    "type": "default"
  },
  {
    "name": "테스트0501-3",
    "type": "default"
  },
  {
    "name": "thelatest saved one",
    "type": "default"
  },
  {
    "name": "테스트45",
    "type": "default"
  },
  {
    "name": "dupleCheck23",
    "type": "default"
  },
  {
    "name": "ffff",
    "type": "default"
  },
  {
    "name": "두",
    "type": "default"
  }
]
}

 

아래처럼 List<Customer>가 응답 타입이 되는 것이 맞을 것 같습니다. ArrayList보다는 List가 ReadOnly이기 때문에 선호되는 타입니다.

@GET("/loadCustomers")
  fun loadCustomers(
      @Query("type") type: String,
      @Query("keyword") keyword: String,
      @Query("type2") type2: String,
      @Query("keyword2") keyword2: String,
  ): Call<List<Customer>>

 

List<Customer>를 type alias로 해서 

type alias LoadCustoomerResposne = List<Customer>

@GET("/loadCustomers")
  fun loadCustomers(
      @Query("type") type: String,
      @Query("keyword") keyword: String,
      @Query("type2") type2: String,
      @Query("keyword2") keyword2: String,
  ): Call<LoadCustoomerResposne>

로 사용할 수는 있지만, 개인적으로는 type alias는 자칫 잘못 사용하면 코드 관리를 아주 헷가릴게 할 수 있어서, 권장드리고 싶지 않네요.

 

참고로 class 이름 LoadCustomerResponse처럼 대문자로 시작하게 짓는 것이 코틀린(자바)의 표준 명명법입니다.

 

spark (224,800 포인트) 님이 2021년 6월 27일 답변
안드로이드촙오자님이 2021년 6월 28일 채택됨
선생님... 말씀 주신대로 해봤지만...
여전히... Expected BEGIN_ARRAY but was STRING at line 2 column 1 path $
이 오류가 나옵니다...
에러메세지가 Expected BEGIN_ARRAY but was STRING at line 2 column 1 path $
이렇게 되어 있습니다. 님이 올리신 응답 JSON은 실제 통신에 사용되는 값과는 좀 다른 포맷으로 보입니다. 에러메세지에는 배열로 시작해야 하는데 문자열 타입이라고 되어 있거든요.
님의 올리신 JSON은 "["로 시작하고  "]'로 끝나는 Array 타입이거든요.
제가 알기로 Firebase는 JsonTree를 사용하는 데이터베이스로 배열이 제일 상위레벨에 응답포맷으로 오는 경우가 없는 걸로 압니다. 무조건  Object타입일 거예요.
로그캣 같은데서 실제 리턴되는 JSON을 확인해 보세요. 그리고 Firebase의 경우는 SDK가 제공되기 때문에 가능하면  SDK를 이용하는 것이 좀 더 나은 선택이라고 보여집니다.
혹시 Firebase에 위에서 말씀하신 JSON string자체를 저정해서 사용하시나요?
Firebase database 구조를 올려보세요.
일단 말씀주신 realtime db의 child인 "customers"의 Json 구조입니다!
 
"customers" : {
    "-MZcLe64fA4r2L-jjnBe" : {
      "customerMemo" : "ㅅㄷㅅㄷㅅ34457ghk",
      "customerName" : "테스트0501-45",
      "customerNumber" : "010-3003-0303",
      "grade" : "default",
      "history" : 0,
      "keyValue" : "-MZcLe64fA4r2L-jjnBe",
      "noshowCount" : 0,
      "photoURL" : "",
      "sales" : 0,
      "savedate" : "20210501233459"
    },
    "-MZcLimNV7OB85U9O6L8" : {
      "customerMemo" : "ㅅㄷ슷",
      "customerName" : "테스트0501-3",
      "customerNumber" : "010-5485-2222",
      "grade" : "default",
      "history" : 0,
      "keyValue" : "-MZcLimNV7OB85U9O6L8",
      "noshowCount" : 0,
      "photoURL" : "",
      "sales" : 0,
      "savedate" : "20210501233518"
    }
또한 말씀주신  "["로 시작하고  "]'로 끝나는 Array 타입 에 대해서는

제가 firebase functions를 활용해서 데이터를 던져주는데

이 때 javascript로

let resultList = [];
...
...
for문...

resultList.put(for문 결과값)
res.send(resultList)

위에 resultList = []; 로 선언을 해서 그런걸까요!?

* 현재 Javascript 코드에 접근할 수 없어서
간략하게 전달드립니다 ㅠㅠ
감사합니다..
그렇군요. Javascript로 한번 처리한 다음 응답을 하시고 계시네요. 그런데, JS로 Firebase데이터를 읽을 때 Firebase는 Map형태인데, 어떻게 Array로 변환을 하시는지요.
customers를 노드를 REST로 읽어오면 customers라는 object이 반환되지 array가 반환되지는 않는데 말이죠. 님이 말씀하신 파이어베이스 구조면 customers 노드안에는 첫번째 customer -MZcLe64fA4r2L-jjnBe만 들어가 있거든요. 따라서 customers를 읽으면  -MZcLe64fA4r2L-jjnBe 데이터만 리턴될 것 같아요.
ref.child(루트).child("customers").orderByChild("grade").equalTo("default")
.once("value").then ~~
으로
"customers" node안에 객체들을 불러오게 되어있습니다.

JS

let resultList = [];
...
...
for(each~){

let object =
{ name = req.qurey.name,
..}

resultList.put(object)
} for문 끝

res.send(resultList)

아니면 Map을 형성해서 데이터를 보내봐도
가장 밖을 싸고 있는게 "[" 대괄호라 못 읽는것 같은데...
다른 방법이 있을까요 ㅠㅠ
그럼,
"customers" : {
    ...
    },
    ..
    }



"customers" : [
   {
    ...
    },
    ..
    }
] //-> missing
를  잘못 카피하신 거란 말씀이시네요.
먼저 Firebae functions에서 마지막에 실제로 어떤 값이 리턴되는지 확인해 보세요.
JS가 정상적으로 배열을 만들었다면 예상하신대로 처리가 되어야 하는데 말이죠.
...