skip to Main Content

In my app, I’m using Retrofit to get the data from API (with flights data). I want to obatin List< Itinerary > from JSON, but the problem is that it is badly formatted, and I’m getting the itineraries seperately. I heard that it’s possible to do that with a Moshi library, but I don’t know how to do that.

data class ItineraryData(
    val itinerary_0: Itinerary0,
    val itinerary_1: Itinerary0,
    val itinerary_2: Itinerary0,
    val itinerary_3: Itinerary0,
    val itinerary_4: Itinerary0,
    val itinerary_5: Itinerary0,
    val itinerary_6: Itinerary0,
    val itinerary_7: Itinerary0,
    val itinerary_8: Itinerary0,
    val itinerary_9: Itinerary0,
)

WHAT I WANT:

data class ItineraryData(
    val itineraries: List<Itinerary0>
)

JSON FRAGMENT

"itinerary_data" : {
      "itinerary_0": {...},
      "itinerary_1": {...},
      "itinerary_2": {...},
      "itinerary_3": {...},
      "itinerary_4": {...},
      "itinerary_5": {...},
      "itinerary_6": {...},
      "itinerary_7": {...},
      "itinerary_8": {...},
      "itinerary_9": {...},
}"

Retrofit App Api:

@Provides
    @Singleton
    fun provideFlightApi(): FlightApi {

        val loggingInterceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

        val client = OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .build()

        return Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build()
            .create(FlightApi::class.java)
    }

getFlights function:

@GET(value = "v2/flight/departures")
    suspend fun getFlights(
        @Query("rapidapi-key") apiKey: String = BuildConfig.API_KEY,
        @Query("departure_date") date: String,
        @Query("adults") passengers: Int,
        @Query("sid") sid: String = "SIFjfID63",
        @Query("origin_city_id") cityDep: String,
        @Query("destination_city_id") cityArr: String,
        @Query("number_of_itineraries") itinerariesCount: Int = 1
    ) : ApiResponse2

2

Answers


  1. Your ItineraryData is a data-transfer object (DTO). It is supposed to represent what you are getting from your server.

    What you want is a model object: an object whose shape is more in line with how you would like to use the data from the server.

    IMHO, those should be separate classes.

    // how you want to use the data
    data class ItineraryModel(
        val itineraries: List<Itinerary0>
    )
    
    // how your server wants to give you the data
    data class ItineraryData(
        val itinerary_0: Itinerary0,
        val itinerary_1: Itinerary0,
        val itinerary_2: Itinerary0,
        val itinerary_3: Itinerary0,
        val itinerary_4: Itinerary0,
        val itinerary_5: Itinerary0,
        val itinerary_6: Itinerary0,
        val itinerary_7: Itinerary0,
        val itinerary_8: Itinerary0,
        val itinerary_9: Itinerary0,
    ) {
      // how you convert between the two
      fun toModel() = ItineraryModel(
        listOf(
          itinerary_0,
          itinerary_1,
          itinerary_2,
          itinerary_3, 
          itinerary_4,
          itinerary_5,
          itinerary_6,
          itinerary_7,
          itinerary_8,
          itinerary_9
        )
      )
    }
    
    Login or Signup to reply.
  2. It sounds like the number of itinerary objects you receive from the server is variable (not necessarily always 10). While it’s unfortunate, then, that the server returns these in a JSON object instead of a variable-length array, a custom Moshi JsonAdapter can read and fix this.

    val json = """
      {
        "itinerary_data" : {
          "itinerary_0": {},
          "itinerary_1": {},
          "itinerary_2": {},
          "itinerary_3": {},
          "itinerary_4": {},
          "itinerary_5": {},
          "itinerary_6": {},
          "itinerary_7": {},
          "itinerary_8": {},
          "itinerary_9": {},
          "itinerary_10": {},
          "itinerary_11": {}
        }
      }
    """.trimIndent()
    
    fun main() {
      // This is the Moshi object to give to your Retrofit converter.
      val moshi = Moshi.Builder()
        .add(ItineraryData.Adapter)
        .build()
      val itineraryDataAdapter = moshi.adapter(ItineraryData::class.java)
      val itineraryData = itineraryDataAdapter.fromJson(json)
    }
    
    @JsonClass(generateAdapter = true)
    data class ItineraryData(
      val itineraries: List<Itinerary>
    ) {
      object Adapter {
        @FromJson
        fun fromJson(
          reader: JsonReader,
          itineraryAdapter: JsonAdapter<Itinerary>
        ): ItineraryData? {
          if (reader.peek() == JsonReader.Token.NULL) {
            return reader.nextNull<ItineraryData?>()
          }
          reader.beginObject()
          var itineraries: MutableList<Itinerary>? = null
          while (reader.hasNext()) {
            when (reader.selectName(options)) {
              0 -> {
                // Found the itinerary_data field.
                reader.beginObject()
                itineraries = mutableListOf()
                while (reader.hasNext()) {
                  val name = reader.nextName()
                  if (name.startsWith("itinerary_")) {
                    itineraries += itineraryAdapter.fromJson(reader)!!
                  } else {
                    // Throw away this non-itinerary field we are not using.
                    reader.skipValue()
                  }
                }
                reader.endObject()
              }
              -1 -> {
                // Throw away this field we are not using.
                reader.skipName()
                reader.skipValue()
              }
              else -> {
                throw AssertionError()
              }
            }
          }
          reader.endObject()
          if (itineraries == null) {
            throw JsonDataException("Missing itinerary_data field.")
          }
          return ItineraryData(unmodifiableList(itineraries))
        }
    
        @ToJson
        fun toJson(
          writer: JsonWriter,
          value: ItineraryData?,
          itineraryAdapter: JsonAdapter<Itinerary>
        ) {
          if (value == null) {
            writer.nullValue()
            return
          }
          writer.beginObject()
          writer.name("itinerary_data")
          writer.beginObject()
          for (i in value.itineraries.indices) {
            val itinerary = value.itineraries[i]
            writer.name("itinerary_$i")
            itineraryAdapter.toJson(writer, itinerary)
          }
          writer.endObject()
          writer.endObject()
        }
    
        private val options = JsonReader.Options.of(
          "itinerary_data"
        )
      }
    }
    
    @JsonClass(generateAdapter = true)
    class Itinerary {
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search