Moshi: анализ одного объекта или списка объектов (котлин)

Я создаю приложение, которое может извлекать объект Warning из API, который я не контролирую, и при этом они не хотят удалять это поведение, с которым я пытаюсь обойти.

Я хотел бы иметь возможность анализировать либо один объект Warning, либо объект List<Warning>. (Поскольку API возвращает либо 1 объект, если имеется только 1, либо> 1 список объектов.)

Вопрос

Как я могу проанализировать объект как список напрямую или проанализировать его с помощью Moshi?

Данные, которые я получаю, выглядят так:

{
  # List<Warning>
  "warnings": [{...}, {...}]
}

или вот так:

{
  # Warning
  "warnings": {...}
}

Я прочитал и нашел несколько источников по этой теме, но нет примеров, и я не уверен, что делать дальше ... так что любые указатели будут очень признательны.

Пытаться

Изначально я пытался вставить автогенерированный адаптер моши, думая, что смогу построить на нем, но я не мог понять, что происходит, так что на самом деле это меня ни к чему не привело. Но я все равно включил сгенерированный код, если он все еще может быть полезен для задачи.

Редактировать: Это то, что у меня есть сейчас, хотя я не очень доволен тем фактом, что для работы требуется экземпляр Moshi.

Решение

Обобщенный подход с фабрикой

Пытался перенести адаптер, который написал Эрик на котлин поскольку с тех пор я понял, что более общий подход лучше, чем Эрик указывает в своем ответе. Как только это сработает, я перепишу части этого поста, чтобы сделать его более понятным, теперь это немного запутано, я прошу прощения за это.

РЕДАКТИРОВАТЬ: В итоге я использовал решение, предложенный Эриком в другом потоке, но перенесенный на Kotlin.

Адаптер с заводом

class SingleToArrayAdapter(
    val delegateAdapter: JsonAdapter<List<Any>>,
    val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {

    companion object {
        val INSTANCE = SingleToArrayAdapterFactory()
    }

    override fun fromJson(reader: JsonReader): Any? =
        if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
            Collections.singletonList(elementAdapter.fromJson(reader))
        } else delegateAdapter.fromJson(reader)

    override fun toJson(writer: JsonWriter, value: Any?) =
        throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")

    class SingleToArrayAdapterFactory : JsonAdapter.Factory {
        override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
            val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java)
            if (delegateAnnotations == null) return null
            if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may be annotated with @SingleToArray. Found: $type")
            val elementType = Types.collectionElementType(type, List::class.java)
            val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
            val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)

            return SingleToArrayAdapter(delegateAdapter, elementAdapter)
        }
    }
}

Квалификатор

Примечание: мне пришлось добавить Target(FIELD).

@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray

использование

Аннотируйте поле, которое вы хотите убедиться, что оно проанализировано как список с помощью @SingleToArray.

data class Alert(
    @SingleToArray
    @Json(name = "alert")
    val alert: List<Warning>
)

и не забудьте добавить адаптер в свой экземпляр moshi:

val moshi = Moshi.Builder()
            .add(SingleToArrayAdapter.INSTANCE)
            .build()

Связанные вопросы / темы:

11
0
5 757
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

the API returns either 1 object if there is only 1 or > 1 a list of objects.

Создайте адаптер, который сначала проверяет, получаете ли вы массив. Здесь - это именно то, что вам нужно. Он включает квалификатор, поэтому вы можете применять его только к спискам, которые могут иметь такое поведение для отдельных элементов. @SingleToArray List<Warning>.

Есть еще один пример работы с несколькими форматами здесь для дальнейшего чтения.

Эрик, спасибо за ваш ответ - ссылки, которые вы разместили, действительно то, что я ищу, и я действительно получил что-то «подобное», однако мне кажется, что я все еще что-то упускаю. Я обновил свой ответ с помощью текущего кода адаптера, который я использую, он работает, но кажется грязным ... что вы думаете?

sphrak 19.11.2018 08:46

Используйте JsonAdapter.Factory, и вам не нужно будет передавать экземпляр Moshi.

Eric Cochran 19.11.2018 12:16

Я попытался использовать Factory, чтобы избежать экземпляра moshi, но каким-то образом это полностью изменило поведение адаптера, но я думаю, что я близок, мне, вероятно, просто не хватает чего-то тривиального. Тем временем я добавил код для справки.

sphrak 21.11.2018 16:24

В конце концов я заставил его работать, забыл очистить проект между сборками. Я благодарю вас и также принял ваш ответ, поскольку в конечном итоге я использовал именно его :)

sphrak 26.11.2018 11:49

Другие вопросы по теме