Я использую 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, но я пробовал разные вещи, но безрезультатно, что у меня нет других идей.