Разбор XML с дооснащением2. Несколько списков результатов не работают - PullRequest
0 голосов
/ 11 апреля 2020

Я использую retrofit2 V2.6.0 в качестве клиента REST для подключения к API Goodreads. Поскольку API Goodreads возвращает данные в XML, я использую Simple XML V2.6.0 в качестве конвертера. Я застрял при конвертации xml в POJO. Что странно, так это то, что он хорошо работает, когда получается книга в единственном числе, но в противном случае отображается ошибка, приведенная ниже (последний фрагмент кода). Книга в xml представлена ​​как работа . Если результат содержит один рабочий элемент, он работает и правильно преобразует XML, в противном случае это не так.

Изменить начало

I обнаружили, что даже запрос с двумя результатами хорошо десериализован. Четыре результата вызывают одно и то же исключение. Были выполнены следующие запросы:

1 Результат https://www.goodreads.com/search/index.xml?key=XXXX&q=tesfi

2 Результаты https://www.goodreads.com/search/index.xml?key=XXXX&q=tesfu

4 Результаты https://www.goodreads.com/search/index.xml?key=XXXX&q=tesff

Редактировать Конец

Мне нужен только поиск API, а ниже приведена ссылка и расшифровка того, как выглядит ответ.

https://www.goodreads.com/search/index.xml?key=XXXXXXXX&q=test

<GoodreadsResponse>
  <Request>
    <authentication>true</authentication>
    <key>
      <![CDATA[ lO9hYshON1dw3N798dkCWg ]]>
    </key>
    <method>
      <![CDATA[ search_index ]]>
    </method>
  </Request>
  <search>
    <query>
      <![CDATA[ test ]]>
    </query>
    <results-start>1</results-start>
    <results-end>20</results-end>
    <total-results>132594</total-results>
    <source>Goodreads</source>
    <query-time-seconds>0.05</query-time-seconds>
    <results>
      <work> // treat this as book mentioned above, if only one of these exists the code works.
        <id type="integer">14262366</id>
        <books_count type="integer">55</books_count>
        <ratings_count type="integer">115931</ratings_count>
        <text_reviews_count type="integer">6845</text_reviews_count>
        <original_publication_year type="integer">2011</original_publication_year>
        <original_publication_month type="integer">5</original_publication_month>
        <original_publication_day type="integer">12</original_publication_day>
        <average_rating>3.94</average_rating>
        <best_book type="Book">
          <id type="integer">12391521</id>
          <title>
            The Psychopath Test: A Journey Through the Madness Industry
          </title>
          <author>
            <id type="integer">1218</id>
            <name>Jon Ronson</name>
          </author>
          <image_url>
            https://i.gr-          assets.com/images/S/compressed.photo.goodreads.com/books/1364166270l/12391521._SX98_.jpg
          </image_url>
          <small_image_url>
            https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1364166270l/12391521._SX50_.jpg
          </small_image_url>
        </best_book>
      </work>
      <work>...</work>
    </results>
  </search>
</GoodreadsResponse>

Ниже приведен мой код POJO:

@Root(name = "GoodreadsResponse", strict = false)
data class SearchResultsResponse @JvmOverloads constructor(
    @field:Element(name = "Request") @param:Element(name = "Request") var Request: Request,
    @field:Element(name = "search", required = false) @param:Element(name = "search", required = false) var search: SearchResults
)

@Root(strict = false, name = "Request")
data class Request @JvmOverloads constructor(
    @field:Element(name = "method") @param:Element(name = "method") var method: String,
    @field:Element(name = "key") @param:Element(name = "key") var key: String,
    @field:Element(name = "authentication", required = false) @param:Element(name = "authentication", required = false) var authentication: String
)

@Root(strict = false, name = "search")
data class SearchResults @JvmOverloads constructor(
    @field:Element(name = "query") @param:Element(name = "query") var query: String,
    @field:Element(name = "results-start", required = false) @param:Element(name = "results-start", required = false)  var start: Int,
    @field:Element(name = "results-end", required = false) @param:Element(name = "results-end", required = false)  var end: Int,
    @field:Element(name = "total-results", required = false) @param:Element(name = "total-results", required = false)  var total: Int,
    @field:Element(name = "source", required = false) @param:Element(name = "source", required = false) var source: String,
    @field:Element(name = "query-time-seconds", required = false) @param:Element(name = "query-time-seconds", required = false) var queryTimeSeconds: String,
    // The below commented lines are other things I have tried
    //@field:ElementList(entry = "results", required = false) @param:ElementList(entry = "results", required = false) var results: List<SearchResult>? = null
    //@field:ElementList(name = "results", entry = "work", type = SearchResult::class, required = false) var results: List<SearchResult>? = null
    //@field:ElementList(name = "results", inline = true, type = SearchResult::class, required = false) var results: List<SearchResult>? = null
    //@field:ElementList(name = "results", inline = false, type = SearchResult::class, required = false) var results: List<SearchResult>? = null
    @field:ElementList(name = "results", entry = "work", type = SearchResult::class, required = false) var results: List<SearchResult>? = null
)

@Root(strict = false, name = "work")
data class SearchResult @JvmOverloads constructor(
    @field:Element(name = "id", required = false) @param:Element(name = "id", required = false) var id: Int,
    @field:Element(name = "books_count", required = false) @param:Element(name = "books_count", required = false) var booksCount: Int,
    @field:Element(name = "ratings_count", required = false) @param:Element(name = "ratings_count", required = false) var ratingsCount: Int,
    @field:Element(name = "text_reviews_count", required = false) @param:Element(name = "text_reviews_count", required = false) var textReviewsCount: Int,
    @field:Element(name = "original_publication_year", required = false) @param:Element(name = "original_publication_year", required = false) var originalPublicationYear: Int,
    @field:Element(name = "original_publication_month", required = false) @param:Element(name = "original_publication_month", required = false) var originalPublicationMonth: Int,
    @field:Element(name = "original_publication_day", required = false) @param:Element(name = "original_publication_day", required = false) var originalPublicationDay: Int,
    @field:Element(name = "average_rating", required = false) @param:Element(name = "average_rating", required = false) var averageRating: Float,
    @field:Element(name = "best_book", required = false) @param:Element(name = "best_book", required = false) val book: Book
)

@Root(name = "best_book", strict = false)
data class Book @JvmOverloads constructor(
    @field:Element(name = "id") @param:Element(name = "id") var id: Int,
    @field:Element(name = "title", required = false) @param:Element(name = "title", required = false) var title: String,
    @field:Element(name = "image_url", required = false) @param:Element(name = "image_url", required = false) var imageUrl: String,
    @field:Element(name = "small_image_url", required = false) @param:Element(name = "small_image_url", required = false) var smallImageUrl: String,
    @field:Element(name = "author", required = false) @param:Element(name = "author", required = false) var author: Author
)

@Root(strict = false, name = "author")
data class Author @JvmOverloads constructor(
    @field:Element(name = "id") @param:Element(name = "id") var id: Int,
    @field:Element(name = "name", required = false) @param:Element(name = "name", required = false) var name: String
)

Класс, который реализует запрос: Здесь я использовал несколько способов преобразования данных

@SuppressWarnings("deprecation")
class FeedController : Callback<SearchResultsResponse> {

    fun run(query: String){

        val persister = Persister(AnnotationStrategy())

        val retrofit = Retrofit.Builder()
            .addCallAdapterFactory(
                RxJava2CallAdapterFactory.create())
            .addConverterFactory(
                SimpleXmlConverterFactory.create(persister))
                /** Tried with:
                 * SimpleXmlConverterFactory.createNonStrict(persister)
                 * SimpleXmlConverterFactory.createNonStrict()
                 * SimpleXmlConverterFactory.create()
                 * **/
            .baseUrl("https://www.goodreads.com/")
            .build()

        val goodreadsApiService = retrofit.create(GoodreadsApiService::class.java)

        val call:Call<SearchResultsResponse> = goodreadsApiService.getResults("XXXXXXXXX", query)

        call.enqueue(this)

    }

    override fun onResponse(call: Call<SearchResultsResponse>, response: Response<SearchResultsResponse>){
        if (response.isSuccessful){
            Log.d("RETROMESSAGE", response.body().toString())
        } else {
            Log.d("RETROMESSAGE", "Error: "+response.errorBody())
        }
    }



    override fun onFailure(call: Call<SearchResultsResponse>, t: Throwable){
        t.printStackTrace()
    }

}
Caused by: org.simpleframework.xml.core.PersistenceException: Constructor not matched for class com.xxxx.goodreads.model.SearchResult
at org.simpleframework.xml.core.ClassInstantiator.getInstance(ClassInstantiator.java:112)
at org.simpleframework.xml.core.Composite$Injector.readInject(Composite.java:1458)
at org.simpleframework.xml.core.Composite$Injector.readInject(Composite.java:1458)

Я подозреваю, что это как-то связано с аннотацией ElementList, но я пробовал разные вещи, но безрезультатно, что у меня нет других идей.

1 Ответ

0 голосов
/ 12 апреля 2020

Узнав, что запрос работает даже с двумя записями, я понял, что проблема не в количестве возвращаемых результатов, а в том, какие результаты возвращены.

В PORO SearchResults я установил originalPublicationYear, originalPublicationMonth и originalPublicationDay как целые числа, и когда они были возвращены пустыми, он устанавливал значение PersistenceException. Это было исправлено путем удаления их в моем случае, так как они мне не нужны. Однако я считаю, что это можно исправить другими способами, если они вам все еще нужны.

...