使用Retrofit2解析XML。多个结果列表不起作用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Retrofit2解析XML。多个结果列表不起作用相关的知识,希望对你有一定的参考价值。

我正在使用Retrofit2 V2.6.0作为REST客户端来连接Goodreads的api。由于Goodreads的api返回XML格式的数据,因此我将SimpleXML V2.6.0用作转换器。我一直坚持将xml转换成POJO。奇怪的是,当结果是一本书时,它会很好地工作,但是在下面显示错误(最后一个代码片段)。 xml中的书表示为work。当结果包含一个work元素时,它将起作用并正确地转换XML,否则不会。

编辑开始

我发现,即使是具有两个结果的查询也可以很好地反序列化。四个结果调用相同的异常。尝试的查询如下。

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注释有关,但是我尝试过,所以可能会有所不同,结果是我没有其他想法了。

答案

[发现查询即使有两个记录也可以使用后,我意识到这不是返回结果数量的问题,而是返回结果的位置。

在SearchResults POJO中,我将originalPublicationYearoriginalPublicationMonthoriginalPublicationDay设置为整数,当它们返回为空时,将关闭PersistenceException。由于我不需要它们,因此通过删除它们来解决此问题。但是我相信,如果您仍然需要它们,可以通过其他方式解决。

以上是关于使用Retrofit2解析XML。多个结果列表不起作用的主要内容,如果未能解决你的问题,请参考以下文章

xml dom 解析器然后使用列表视图显示结果

如何使用多个属性和选项列表解析 XML

在 C# 中使用 LINQ-To-XML 解析具有多个列表和类对象的 XML 数据

在Python中解析多个xml文件

具有多个连接/提要/视图的 XML 解析的设计/实施建议

启用 Pro Guard Jackson 解析器后不起作用