• 카테고리

    질문 & 답변
  • 세부 분야

    모바일 앱 개발

  • 해결 여부

    미해결

Json 형식 데이터 모델 설계 질문드립니다 :)

23.02.02 13:55 작성 23.02.02 14:08 수정 조회수 1.04k

2

안녕하세요! 항상 좋은 강의 찍어주셔서 감사하다는 말씀부터 드립니다 :)

다름이 아니라 제가 강의를 보고 혼자서 따라하고 있는데 막히는 부분이 있어서 질문 드립니다 !

제가 막히는 부분은 Open RestAPI(https://restcountries.com/v3.1/all)를 이용을 하여 데이터를 가지고 오려고 하는데 이걸 가지고 오게 해주는 데이터 모델 설계 부분에서 막힙니다. !!

제가 설계한 데이터 모델 설계은 Json 형식으로 되어 있는것을 자동으로 변환해주는 안드로이드 스튜디오 플러그인(Json to Kotlin class)을 이용을 하게 되는데요! 간단한 Json 형태로 되어 있다면 문제가 없이 잘 설계를 해주더라구요!

EX) 제가 생각한 간단한 Json 형태

{
	"student" : [
		{
			"student_id": 30410,
			"name" : "홍길동",
			"phone" : "010-12345-1234"
		},
		{
			"student_id": 30411,
			"name" : "고길동",
			"phone" : "010-53455-1256"
		},
		{
			"student_id": 30413,
			"name" : "둘리",
			"phone" : "010-35243-5345"
		},
		{
			"student_id": 30414,
			"name" : "아이유",
			"phone" : "010-13352-5343"
		}
	]
}

위와 같은 형식은 예를들어 respose.body.studuent.student_id 이렇게 모든 값들이 찍히는것을 알 수 있습니다.

하지만 제가 사용하려는 Json 형식의 데이터 형태들은 간단하지 않고, 특정 값을 입력을 해야하는데 이 값이 특정되게 입력해줄 수 없는 형태인데요! 제가 안드로이드 스튜디오 플러그인(Json to Kotlin class)을 이용을 하여 제가 사용하려는 JSON 형태를 data class로 변환을 하여 만들었는데 엄청나게 많은 클래스(500개)가 생겨났습니다. 이유는 모든 데이터 클래스를 생성하여 그에 따른 가능성을 모두 생성하여 대입해줬기 때문입니다.
제가 생각해본 방법은 Map을 이용하면 될 것 같은데 1주일동안 고민하고 해결해보려고 했지만 성공하지 못했습니다. 혹시 방법이 있을까요?

EX) 내가 사용하려는 복잡한 구조의 Json 형태

[

  {
  
    "name": {
      "common": "Saint Pierre and Miquelon",
      "official": "Saint Pierre and Miquelon",
      "nativeName": {
        "fra": {
          "official": "Collectivité territoriale de Saint-Pierre-et-Miquelon",
          "common": "Saint-Pierre-et-Miquelon"
        }
      }
    },

    "currencies": {

      "EUR": {

        "name": "Euro",

        "symbol": "€"

      }

    },

    "translations": {

      "ara": {

        "official": "سان بيير وميكلون",

        "common": "سان بيير وميكلون"

      },

      "bre": {

        "official": "Sant-Pêr-ha-Mikelon",

        "common": "Sant-Pêr-ha-Mikelon"

      },

      "ces": {

        "official": "Saint-Pierre a Miquelon",

        "common": "Saint-Pierre a Miquelon"

      }

    },

    "languages": {

      "fra": "French"

    }

  }

]

위는 제가 현재 사용하려는 Json 형태의 일부분 입니다. name-common 같은 부분은 it.name.common을 사용하게 되면 문제없이 사용이 가능하고 생성되는 data class도 적습니다. 이유는 name-common 이라는 필드가 모든 나라에 대해서 공통으로 사용하고 있어서 고정값으로 사용할 수 가 있기 때문입니다.
하지만 name-nativeName - "fra" 필드를 보게되면 특정한 값인 fra(위의 예시의 경우)를 특정되게 입력을 해줘야 값을 받아올 수 있습니다. 이러한 "fra"라는 값은 이 나라의 고유의 값이 아니라 모든 나라에서 사용하는 언어 중 한 개 입니다. 그래서 @Path를 이용해 매개변수에 정확하게 입력해줄 수 있는것이 아니라 가능성(모든 나라의 언어)에 대해 모든것을 대입해야만 했습니다. 모든것을 대입해야만 했기 때문에 data class가 매우 많이 생성되는것을 겪었습니다.
( nativeName 필드뿐 아니라 languages 필드를 보더라도 특정되게 "fra"를 찝어야 가져 올 수 있습니다. )

아래는 제가 생성한 data model class 입니다.
구조는 Country_Response_Detail - Country-ResponseItem)Detail - [ capital, cioc, currencies, languages, name, population, region] 으로 해서 뻗어 나가는 구조입니다 !.

 if(it.capital==null) {
                            val capital = ""
                        }
                        else {
                            val capital = it.capital.toString().substring(1,it.capital.toString().length-1) //activity_countries_detail.xml 사용 할 capital(수도) 변수
                            capital_Input_Detail.text = capital //capital_Input_Detail이라는 TextView와 capital 변수 연결
                        }

if (it.currencies==null) {
                            currency_Input_Detail.text = "" //currency_Input_Detail이라는 TextView와 currency 변수 연결
                        } else {
                            val currency_Index1 = it.currencies.toString().indexOf("name") + 5
                            val currency_Index2 = it.currencies.toString().indexOf(",",currency_Index1)
                            val currency_Index3 = it.currencies.toString().indexOf("symbol") + 7
                            val currency_Index4 = it.currencies.toString().indexOf(")",currency_Index1)

                            val currencies_Name = it.currencies.toString().substring(currency_Index1,currency_Index2)
                            val currencies_Symbol = it.currencies.toString().substring(currency_Index3,currency_Index4)
                            val currency = StringBuilder().append(currencies_Symbol).append("(").append(currencies_Name).append(")").toString() //activity_countries_detail.xml 사용 할 currency(화폐) 변수
                            println("currency_Input : ${currency}")
                            currency_Input_Detail.text = currency //currency_Input_Detail이라는 TextView와 currency 변수 연결
                        }

위에서 만든 data class를 통해 데이터를 받아오면 null 값도 전부 받아오기 때문에..이렇게 데이터를 정제해서 쓰고 있습니다..
혹시 편하게 그냥 null 값이 아니면 그 데이터의 값만 나오게 가능할 지도 궁금합니다..

 

저는 이러한 문제에 직면을 하고 있습니다..
두서없이 적어서 이해가 안되실것입니다 ㅜㅜ.. 도움을 주시면 정말 감사하겠습니다 :)

 

답변 1

답변을 작성해보세요.

1

안녕하세요 병준님.

저... 사실 잘 이해가 가지 않는데요.

혹시 말씀해주신 api주소에서

https://restcountries.com/v3.1/all

어떤 정보를 정확하게 가져와야 하는것인가요?

또한 api 문서에서 특정 값을 가져오는 방법은 없나요?

혹시 영상으로 설명해주실 수 있다면 유튜브에 올려주시고 링크를 공유해주시면 함께 고민해볼 수 있을 것 같습니다.

영상 촬영해 블로그에 올렸습니다!
같이 고민해주시면 감사하겠습니다!

( https://bj-turtle.tistory.com/111 )

병준님

질문 잘 올려주셔서 감사합니다만.. 사실 질문이 잘 이해가 가지 않습니다.

 

여기 language를 보면 특정 나라별로 가져오는 것이 있는데, 왜 모든 데이터를 가져오는 것일까요?

 

image

 

보내주신 language 필터 url을 이용을 하여 사용하게 되면 특정 국가에 대한 정보는 나오게 되는것은 맞습니다.
하지만 이것도 마찬가지로 language의 값을 가져오기 위해선 특정 키 값(예를 들어, 한국의 경우 kor, 미국의 경우 eng, 다른 나라의 경우도 eng를 씀, 그렇기에 현재 저는 특정 키 값을 입력을 시켜줄 방법이 없어서 일일히 전부 키 값에 대한 가능성을 대입을 시켜주고 있음)을 입력을 시켜줘야 데이터를 가져오게 됩니다.

제가 여태까지 빙빙 둘러서 말했는데.. 혹시 나라마다 다른 language의 특정 키 값에 대한 정보를 어떻게 입력을 시켜주면 될까요..? (나라마다 다른 language의 특정 키 값을 입력을 해야 하는데 이 부분을 어떻게 해결하면 될까요ㅜㅜ)

image

https://stackoverflow.com/questions/40973633/retrofit-2-get-json-from-response-body

 

위와 같은 방식으로 laguages를 가져와서 각각 language 별로 api 호출을 하면 되지 않을까 하는 생각을 하는데, 혹시 이와 같은 방식으로는 힘들까요?

보내주신 링크를 천천히 읽어봤는데, response의 null 값 처리 인 것 같습니다 ㅜㅜ..

저는 response를 ArrayList로 정상적 받아 오긴합니다.
response를 받아오게 되면 ArrayList의 index에 해당하는 값은 나라(큰 틀)입니다. 그 나라 안에 필드 값 들(작은 틀,원하는 데이터) 중에 제가 원하는 language가 있는데 이 값을 가져오기 위해선 key 값이 필요한데 이것을 특정되게 넣을 수 없다는 것입니다.. 나라안의 다른 필드 값(예로 capital)들은 그냥 키 값이 없어 그대로 사용하면 되는데.. 특정 key 값이 있는 필드들은 어떻게 사용할까요?
language의 경우 제가 어떻게든 나라안의 데이터를 이용해 키 값을 넣어보려고 했지만 이 키 값은 언어라서 그런지 규칙성이 없었습니다...

계속 질문 드려서 죄송합니다 ...

요거는 제가 직접 코드를 한번 작성해보겠습니다

퇴근하고 한번 살펴보겠습니다.

안녕하세요 답변이 늦어서 죄송합니다.

위의 링크에서 Any Type으로 데이터를 가져와서 아래와 같이 사용하시는 것을 추천드리려고 했는데

 

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val api = RetrofitInstance.getInstance().create(MyApi::class.java)

        api.getPost1().enqueue(object : Callback<Any> {
            override fun onResponse(call: Call<Any>, response: Response<Any>) {
                Log.d("API1", response.toString())
                Log.d("API1", response.body().toString())


                val jsonObject = JSONObject(Gson().toJson(response.body()))

                Log.d("API1", jsonObject.getString("userId"))

                
            }

            override fun onFailure(call: Call<Any>, t: Throwable) {
                Log.d("API1", "Error")
            }

        })
        

    }
}
object RetrofitInstance {

    val BASE_URL = "https://jsonplaceholder.typicode.com/"
    
    val client = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    fun getInstance() : Retrofit {
        return client
    }

}

 

interface MyApi {

    @GET("posts/1")
    fun getPost1() : Call<Any>

}

저 Json 데이터가 너무 커서 처리하는데 예외가 떠버리네요..!

이럴 때는 저 JSON 데이터를 모두 로컬에 파일로 저장해놓고, 실시간으로 통신하지 않고, 로컬에 있는 파일을 읽어와서 처리하는 형태로 처리하시는건 어떠실까요?

 

https://stackoverflow.com/questions/56962608/how-to-read-json-file-from-assests-in-android-using-kotlin

 

 

아하 Any Type이 있었군요..! 직접 코드로 구현까지 해주시고 정말 감사드립니다 :) 많은 도움이 되었습니다 ㅎㅎ

로컬에 파일로 저장후 처리하는 형태로 해봐야 겠군요..!


실시간 통신으로는 제가 원하는 Language 필드의 값을 가져오기 위해선, API를 만든쪽에서 별도의 Language 필터 API를 다시 짜줘야 해결 가능하겠죠..?!

넵 보통 서버개발자와 협업하는 형태로 진행하기 때문에, 그 방법으로 하시는게 맞으실 듯 합니다 :)

정말 많은 도움이 되었습니다 :)

강의도 너무나 핵심적인 부분을 집어서 해주셔서 큰 도움이 되고 있습니다 ~!