Android Retrofit 2 multiple converters (Gson & SimpleXML) 错误
Android Retrofit 2 multiple converters (Gson & SimpleXML) error
在我的项目中(我正在使用 Dagger 2、Retrofit 2 和 OkHTTP、RxAndroid)我有 2 个不同的 API 调用。从一个我收到 JSON,从另一个 - XML。我研究了 this link 并在 Retrofit.Builder():
中添加了我需要的 2 个转换器
@Provides
@Singleton
public Gson providesGson() {
return new GsonBuilder().create();
}
@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient, @NonNull Gson gson) {
return new Retrofit.Builder()
.baseUrl(ConstantsManager.BASE_URL)
.client(okHttpClient)
.addConverterFactory(SimpleXmlConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
但是,当我收到 JSON(XML 被正确转换)时,我得到以下信息:
D/OkHttp: {"date":"20.11.2016","bank":"PB","baseCurrency":980,"baseCurrencyLit":"UAH","exchangeRate":[{"baseCurrency":"UAH","currency":"AUD","saleRateNB":19.4452740,"purchaseRateNB":19.4452740},{"baseCurrency":"UAH","currency":"CAD","saleRateNB":19.4047320,"purchaseRateNB":19.4047320},{"baseCurrency":"UAH","currency":"CZK","saleRateNB":1.0322170,"purchaseRateNB":1.0322170,"saleRate":1.0800000,"purchaseRate":0.9800000},{"baseCurrency":"UAH","currency":"DKK","saleRateNB":3.7519280,"purchaseRateNB":3.7519280},{"baseCurrency":"UAH","currency":"HUF","saleRateNB":0.0902556,"purchaseRateNB":0.0902556},{"baseCurrency":"UAH","currency":"ILS","saleRateNB":6.7524710,"purchaseRateNB":6.7524710,"saleRate":7.0000000,"purchaseRate":6.3000000},{"baseCurrency":"UAH","currency":"JPY","saleRateNB":0.2384005,"purchaseRateNB":0.2384005,"saleRate":0.2500000,"purchaseRate":0.2200000},{"baseCurrency":"UAH","currency":"LVL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"LTL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"NOK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120,"saleRate":3.2000000,"purchaseRate":2.9000000},{"baseCurrency":"UAH","currency":"SKK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120},{"baseCurrency":"UAH","currency":"SEK","saleRateNB":2.8384710,"purchaseRateNB":2.8384710},{"baseCurrency":"UAH","currency":"CHF","saleRateNB":26.0049080,"purchaseRateNB":26.0049080,"saleRate":27.5000000,"purchaseRate":25.0000000},{"baseCurrency":"UAH","currency":"RUB","saleRateNB":0.4013400,"purchaseRateNB":0.4013400,"saleRate":0.4200000,"purchaseRate":0.4000000},{"baseCurrency":"UAH","currency":"GBP","saleRateNB":32.4460750,"purchaseRateNB":32.4460750,"saleRate":34.0000000,"purchaseRate":31.0000000},{"baseCurrency":"UAH","currency":"USD","saleRateNB":26.0534380,"purchaseRateNB":26.0534380,"saleRate":27.0000000,"purchaseRate":26.6000000},{"baseCurrency":"UAH","currency":"BYR","saleRateNB":26.0534380,"purchaseRateNB":26.0534380},{"baseCurrency":"UAH","currency":"EUR","saleRateNB":27.9214700,"purchaseRateNB":27.9214700,"saleRate":28.6000000,"purchaseRate":28.2000000},{"baseCurrency":"UAH","currency":"GEL","saleRateNB":10.5921530,"purchaseRateNB":10.5921530},{"baseCurrency":"UAH","currency":"PLZ","saleRateNB":6.2818280,"purchaseRateNB":6.2818280,"saleRate":6.6000000,"purchaseRate":5.9000000}]}
D/OkHttp: <-- END HTTP (2377-byte body)
E/DateCurrencyService: Error while loading data occurred!
java.lang.RuntimeException: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:44)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965)
at org.kxml2.io.KXmlParser.next(KXmlParser.java:432)
at org.kxml2.io.KXmlParser.next(KXmlParser.java:313)
at org.simpleframework.xml.stream.PullReader.read(PullReader.java:105)
at org.simpleframework.xml.stream.PullReader.next(PullReader.java:89)
at org.simpleframework.xml.stream.NodeReader.readElement(NodeReader.java:111)
at org.simpleframework.xml.stream.NodeReader.readRoot(NodeReader.java:85)
at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:84)
at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:71)
at org.simpleframework.xml.core.Persister.read(Persister.java:562)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:36)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
据我所知,Simple XML Converter 尝试转换我从第二次调用获得的 JSON 响应(XML 来自第一次调用的响应,因为我前面说过,正确转换)。我需要做的是用适当的转换器转换每个响应(XML 响应简单 XML 转换器和 JSON 响应与 Gson 转换器)
如果我尝试切换添加转换器的顺序,我将在接收 XML 时遇到类似的错误(因为 Gson 转换器试图转换 XML 响应,显然):
D/OkHttp: <exchangerate><exchangerate ccy="EUR" ccy_name_ru="Евро " ccy_name_ua="Євро " ccy_name_en="Euro " base_ccy="UA" buy="27247312" unit="100.00000" date="2016.11.29" /><exchangerate ccy="RUR" ccy_name_ru="Российский рубль " ccy_name_ua="Росiйський рубль " ccy_name_en="Russian Rouble " base_ccy="UA" buy="39810" unit="10.00000" date="2016.11.29" /><exchangerate ccy="USD" ccy_name_ru="Доллар США " ccy_name_ua="Долар США " ccy_name_en="US Dollar " base_ccy="UA" buy="25724426" unit="100.00000" date="2016.11.29" /></exchangerate>
D/OkHttp: <-- END HTTP (799-byte body)
E/SyncService: Error while loading data occurred!
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1559)
at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1401)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:593)
at com.google.gson.stream.JsonReader.peek(JsonReader.java:425)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:205)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
我该怎么做才能使每个转换器转换适当的响应?
更新: 正如 所建议的,我做了以下操作:
1) 添加了自定义转换器 class:
public class RetrofitUniversalConverter extends Converter.Factory {
private final Converter.Factory xml;
private final Converter.Factory json;
@Inject
public RetrofitUniversalConverter(@NonNull Gson gson) {
xml = SimpleXmlConverterFactory.create();
json = GsonConverterFactory.create(gson);
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for (Annotation annotation : annotations) {
if (annotation.getClass() == Xml.class) {
return xml.responseBodyConverter(type, annotations, retrofit);
}
if (annotation.getClass() == Json.class) {
return json.responseBodyConverter(type, annotations, retrofit);
}
}
return null;
}
}
2) 重写了我的 Retrofit 模块:
@Provides
@Singleton
public Gson providesGson() {
return new GsonBuilder().create();
}
@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient,
@NonNull RetrofitUniversalConverter converter) {
return new Retrofit.Builder()
.baseUrl(ConstantsManager.BASE_URL)
.client(okHttpClient)
.addConverterFactory(converter)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
3) 然后,我在我的 API 界面中添加了注释:
public interface PrivatbankApi {
@GET @Xml
Observable<CurrentRates> loadCurrentRates(@NonNull @Url String url);
@GET("exchange_rates") @Json
Observable<DateRates> loadDateRates(@NonNull @Query("json") Boolean json, @NonNull @Query("date") String date);
}
但后来我得到异常:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.vedmedenko.exchangerates, PID: 5432
java.lang.RuntimeException: Unable to start service com.vedmedenko.exchangerates.core.services.SyncService@68cf714 with Intent { flg=0x4 cmp=com.vedmedenko.exchangerates/.core.services.SyncService (has extras) }: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
for method PrivatbankApi.loadCurrentRates
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3343)
at android.app.ActivityThread.-wrap21(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1582)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
for method PrivatbankApi.loadCurrentRates
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:706)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:167)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
at retrofit2.Retrofit.invoke(Retrofit.java:145)
at java.lang.reflect.Proxy.invoke(Proxy.java:813)
at $Proxy0.loadCurrentRates(Unknown Source)
at com.vedmedenko.exchangerates.core.DataManager.loadCurrentRates(DataManager.java:29)
at com.vedmedenko.exchangerates.core.services.SyncService.onStartCommand(SyncService.java:81)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3326)
... 8 more
Caused by: java.lang.IllegalArgumentException: Could not locate ResponseBody converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates.
Tried:
* retrofit2.BuiltInConverters
* com.vedmedenko.exchangerates.core.rest.converters.RetrofitUniversalConverter
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:346)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:308)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:704)
... 16 more
查看 Jake Wharton 的 this presentation,他在其中完全解决了您描述的问题(并提供了许多其他有用的技巧)。
简而言之,他建议创建表示您预期数据格式的注释(例如,在您的情况下为 Json
和 Xml
)并相应地注释您的 API 调用。然后定义自定义 ConverterFactory
,根据遇到的注释委托给 GsonConverterFactory
或 SimpleXmlConverterFactory
。
但是,您需要向 Jake 的解决方案添加两件事。
首先不要忘记用 @Retention(RetentionPolicy.RUNTIME)
注释您的注释,否则它们将不会在运行时保留:
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
其次,您在 responseBodyConverter
方法中收到的注释实际上并不是您的注释。它们是系统为您的注释创建的代理。因此需要替换条件:
annotation.getClass() == Json.class
annotation.getClass() == Xml.class
与:
annotation.annotationType() == Json.class
annotation.annotationType() == Xml.class
感谢@Marcin 和参考。
下面是 Jake Warton 提到的演示文稿中的代码,根据 Marcin 的评论进行了调整(如果未找到注释,则添加 Json 默认工厂):
public class AnnotatedConverterFactory extends Converter.Factory {
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
@Retention(RetentionPolicy.RUNTIME)
public @interface Xml {}
final Map<Class<?>, Converter.Factory> mFactoryMap;
public AnnotatedConverterFactory(Map<Class<?>, Converter.Factory> factoryMap) {
mFactoryMap = factoryMap;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for(Annotation annotation : annotations){
Converter.Factory factory = mFactoryMap.get(annotation.annotationType());
if(factory != null){
return factory.responseBodyConverter(type, annotations, retrofit);
}
}
//try to default to json in case no annotation on current method was found
Converter.Factory jsonFactory = mFactoryMap.get(Json.class);
if(jsonFactory != null){
return jsonFactory.responseBodyConverter(type, annotations, retrofit);
}
return null;
}
static class Builder {
Map<Class<?>, Converter.Factory> mFactoryMap = new LinkedHashMap<>();
Builder add(Class<? extends Annotation> factoryType, Converter.Factory factory){
if(factoryType == null) throw new NullPointerException("factoryType is null");
if(factory == null) throw new NullPointerException("factory is null");
mFactoryMap.put(factoryType, factory);
return this;
}
public AnnotatedConverterFactory build(){
return new AnnotatedConverterFactory(mFactoryMap);
}
}
}
用法:
interface RetrofitService{
@POST
@AnnotatedConverterFactory.Xml
Call<Envelope> someApiMethodWithXmlRespoonse(@Url String url, @Body Envelope envelope);
@POST
@AnnotatedConverterFactory.Json
Call<Envelope> someApiMethodWithJsonRespoonse(@Url String url, @Body Model model);
}
实例化改造:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(mOkHttpClient)
.addConverterFactory(new AnnotatedConverterFactory.Builder()
.add(AnnotatedConverterFactory.Xml.class, SimpleXmlConverterFactory.createNonStrict())
.add(AnnotatedConverterFactory.Json.class, GsonConverterFactory.create(mGson))
.build()
)
mRetrofitService = retrofit.create(RetrofitService.class);
对于 kotlin 用户来说要简单得多
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class XmlResponse
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class JsonResponse
class MultipleConverterFactory(private val factories: Map<Class<*>, Converter.Factory>) : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
return annotations.mapNotNull { factories[it.annotationClass.javaObjectType] }
.getOrNull(0)
?.responseBodyConverter(type, annotations, retrofit)
}
class Builder {
private val factories = hashMapOf<Class<*>, Converter.Factory>()
fun setXmlConverterFactory(converterFactory: Converter.Factory): Builder {
factories[XmlResponse::class.java] = converterFactory
return this
}
fun setJsonConverterFactory(converterFactory: Converter.Factory): Builder {
factories[JsonResponse::class.java] = converterFactory
return this
}
@Suppress("unused")
fun addCustomConverterFactory(
annotation: Class<out Annotation>,
converterFactory: Converter.Factory
): Builder {
factories[annotation] = converterFactory
return this
}
fun build(): MultipleConverterFactory {
return MultipleConverterFactory(factories)
}
}
}
和用法
Retrofit.Builder()
// ...
.addConverterFactory(
MultipleConverterFactory.Builder()
.setXmlConverterFactory(TikXmlConverterFactory.create(instance()))
.setJsonConverterFactory(GsonConverterFactory.create(instance()))
.build()
)
.build()
// some API endpoint
@XmlResponse
@GET
suspend fun getXml(
@Url url: String
): MyModel
在我的项目中(我正在使用 Dagger 2、Retrofit 2 和 OkHTTP、RxAndroid)我有 2 个不同的 API 调用。从一个我收到 JSON,从另一个 - XML。我研究了 this link 并在 Retrofit.Builder():
中添加了我需要的 2 个转换器@Provides
@Singleton
public Gson providesGson() {
return new GsonBuilder().create();
}
@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient, @NonNull Gson gson) {
return new Retrofit.Builder()
.baseUrl(ConstantsManager.BASE_URL)
.client(okHttpClient)
.addConverterFactory(SimpleXmlConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
但是,当我收到 JSON(XML 被正确转换)时,我得到以下信息:
D/OkHttp: {"date":"20.11.2016","bank":"PB","baseCurrency":980,"baseCurrencyLit":"UAH","exchangeRate":[{"baseCurrency":"UAH","currency":"AUD","saleRateNB":19.4452740,"purchaseRateNB":19.4452740},{"baseCurrency":"UAH","currency":"CAD","saleRateNB":19.4047320,"purchaseRateNB":19.4047320},{"baseCurrency":"UAH","currency":"CZK","saleRateNB":1.0322170,"purchaseRateNB":1.0322170,"saleRate":1.0800000,"purchaseRate":0.9800000},{"baseCurrency":"UAH","currency":"DKK","saleRateNB":3.7519280,"purchaseRateNB":3.7519280},{"baseCurrency":"UAH","currency":"HUF","saleRateNB":0.0902556,"purchaseRateNB":0.0902556},{"baseCurrency":"UAH","currency":"ILS","saleRateNB":6.7524710,"purchaseRateNB":6.7524710,"saleRate":7.0000000,"purchaseRate":6.3000000},{"baseCurrency":"UAH","currency":"JPY","saleRateNB":0.2384005,"purchaseRateNB":0.2384005,"saleRate":0.2500000,"purchaseRate":0.2200000},{"baseCurrency":"UAH","currency":"LVL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"LTL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"NOK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120,"saleRate":3.2000000,"purchaseRate":2.9000000},{"baseCurrency":"UAH","currency":"SKK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120},{"baseCurrency":"UAH","currency":"SEK","saleRateNB":2.8384710,"purchaseRateNB":2.8384710},{"baseCurrency":"UAH","currency":"CHF","saleRateNB":26.0049080,"purchaseRateNB":26.0049080,"saleRate":27.5000000,"purchaseRate":25.0000000},{"baseCurrency":"UAH","currency":"RUB","saleRateNB":0.4013400,"purchaseRateNB":0.4013400,"saleRate":0.4200000,"purchaseRate":0.4000000},{"baseCurrency":"UAH","currency":"GBP","saleRateNB":32.4460750,"purchaseRateNB":32.4460750,"saleRate":34.0000000,"purchaseRate":31.0000000},{"baseCurrency":"UAH","currency":"USD","saleRateNB":26.0534380,"purchaseRateNB":26.0534380,"saleRate":27.0000000,"purchaseRate":26.6000000},{"baseCurrency":"UAH","currency":"BYR","saleRateNB":26.0534380,"purchaseRateNB":26.0534380},{"baseCurrency":"UAH","currency":"EUR","saleRateNB":27.9214700,"purchaseRateNB":27.9214700,"saleRate":28.6000000,"purchaseRate":28.2000000},{"baseCurrency":"UAH","currency":"GEL","saleRateNB":10.5921530,"purchaseRateNB":10.5921530},{"baseCurrency":"UAH","currency":"PLZ","saleRateNB":6.2818280,"purchaseRateNB":6.2818280,"saleRate":6.6000000,"purchaseRate":5.9000000}]}
D/OkHttp: <-- END HTTP (2377-byte body)
E/DateCurrencyService: Error while loading data occurred!
java.lang.RuntimeException: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:44)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965)
at org.kxml2.io.KXmlParser.next(KXmlParser.java:432)
at org.kxml2.io.KXmlParser.next(KXmlParser.java:313)
at org.simpleframework.xml.stream.PullReader.read(PullReader.java:105)
at org.simpleframework.xml.stream.PullReader.next(PullReader.java:89)
at org.simpleframework.xml.stream.NodeReader.readElement(NodeReader.java:111)
at org.simpleframework.xml.stream.NodeReader.readRoot(NodeReader.java:85)
at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:84)
at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:71)
at org.simpleframework.xml.core.Persister.read(Persister.java:562)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:36)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
据我所知,Simple XML Converter 尝试转换我从第二次调用获得的 JSON 响应(XML 来自第一次调用的响应,因为我前面说过,正确转换)。我需要做的是用适当的转换器转换每个响应(XML 响应简单 XML 转换器和 JSON 响应与 Gson 转换器)
如果我尝试切换添加转换器的顺序,我将在接收 XML 时遇到类似的错误(因为 Gson 转换器试图转换 XML 响应,显然):
D/OkHttp: <exchangerate><exchangerate ccy="EUR" ccy_name_ru="Евро " ccy_name_ua="Євро " ccy_name_en="Euro " base_ccy="UA" buy="27247312" unit="100.00000" date="2016.11.29" /><exchangerate ccy="RUR" ccy_name_ru="Российский рубль " ccy_name_ua="Росiйський рубль " ccy_name_en="Russian Rouble " base_ccy="UA" buy="39810" unit="10.00000" date="2016.11.29" /><exchangerate ccy="USD" ccy_name_ru="Доллар США " ccy_name_ua="Долар США " ccy_name_en="US Dollar " base_ccy="UA" buy="25724426" unit="100.00000" date="2016.11.29" /></exchangerate>
D/OkHttp: <-- END HTTP (799-byte body)
E/SyncService: Error while loading data occurred!
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1559)
at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1401)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:593)
at com.google.gson.stream.JsonReader.peek(JsonReader.java:425)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:205)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.Subscriber.setProducer(Subscriber.java:205)
at rx.internal.operators.OperatorSubscribeOn.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:8666)
at rx.internal.operators.OperatorSubscribeOn.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker.call(CachedThreadScheduler.java:220)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
我该怎么做才能使每个转换器转换适当的响应?
更新: 正如
1) 添加了自定义转换器 class:
public class RetrofitUniversalConverter extends Converter.Factory {
private final Converter.Factory xml;
private final Converter.Factory json;
@Inject
public RetrofitUniversalConverter(@NonNull Gson gson) {
xml = SimpleXmlConverterFactory.create();
json = GsonConverterFactory.create(gson);
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for (Annotation annotation : annotations) {
if (annotation.getClass() == Xml.class) {
return xml.responseBodyConverter(type, annotations, retrofit);
}
if (annotation.getClass() == Json.class) {
return json.responseBodyConverter(type, annotations, retrofit);
}
}
return null;
}
}
2) 重写了我的 Retrofit 模块:
@Provides
@Singleton
public Gson providesGson() {
return new GsonBuilder().create();
}
@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient,
@NonNull RetrofitUniversalConverter converter) {
return new Retrofit.Builder()
.baseUrl(ConstantsManager.BASE_URL)
.client(okHttpClient)
.addConverterFactory(converter)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
3) 然后,我在我的 API 界面中添加了注释:
public interface PrivatbankApi {
@GET @Xml
Observable<CurrentRates> loadCurrentRates(@NonNull @Url String url);
@GET("exchange_rates") @Json
Observable<DateRates> loadDateRates(@NonNull @Query("json") Boolean json, @NonNull @Query("date") String date);
}
但后来我得到异常:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.vedmedenko.exchangerates, PID: 5432
java.lang.RuntimeException: Unable to start service com.vedmedenko.exchangerates.core.services.SyncService@68cf714 with Intent { flg=0x4 cmp=com.vedmedenko.exchangerates/.core.services.SyncService (has extras) }: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
for method PrivatbankApi.loadCurrentRates
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3343)
at android.app.ActivityThread.-wrap21(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1582)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
for method PrivatbankApi.loadCurrentRates
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:706)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:167)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
at retrofit2.Retrofit.invoke(Retrofit.java:145)
at java.lang.reflect.Proxy.invoke(Proxy.java:813)
at $Proxy0.loadCurrentRates(Unknown Source)
at com.vedmedenko.exchangerates.core.DataManager.loadCurrentRates(DataManager.java:29)
at com.vedmedenko.exchangerates.core.services.SyncService.onStartCommand(SyncService.java:81)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3326)
... 8 more
Caused by: java.lang.IllegalArgumentException: Could not locate ResponseBody converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates.
Tried:
* retrofit2.BuiltInConverters
* com.vedmedenko.exchangerates.core.rest.converters.RetrofitUniversalConverter
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:346)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:308)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:704)
... 16 more
查看 Jake Wharton 的 this presentation,他在其中完全解决了您描述的问题(并提供了许多其他有用的技巧)。
简而言之,他建议创建表示您预期数据格式的注释(例如,在您的情况下为 Json
和 Xml
)并相应地注释您的 API 调用。然后定义自定义 ConverterFactory
,根据遇到的注释委托给 GsonConverterFactory
或 SimpleXmlConverterFactory
。
但是,您需要向 Jake 的解决方案添加两件事。
首先不要忘记用 @Retention(RetentionPolicy.RUNTIME)
注释您的注释,否则它们将不会在运行时保留:
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
其次,您在 responseBodyConverter
方法中收到的注释实际上并不是您的注释。它们是系统为您的注释创建的代理。因此需要替换条件:
annotation.getClass() == Json.class
annotation.getClass() == Xml.class
与:
annotation.annotationType() == Json.class
annotation.annotationType() == Xml.class
感谢@Marcin 和参考。
下面是 Jake Warton 提到的演示文稿中的代码,根据 Marcin 的评论进行了调整(如果未找到注释,则添加 Json 默认工厂):
public class AnnotatedConverterFactory extends Converter.Factory {
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
@Retention(RetentionPolicy.RUNTIME)
public @interface Xml {}
final Map<Class<?>, Converter.Factory> mFactoryMap;
public AnnotatedConverterFactory(Map<Class<?>, Converter.Factory> factoryMap) {
mFactoryMap = factoryMap;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for(Annotation annotation : annotations){
Converter.Factory factory = mFactoryMap.get(annotation.annotationType());
if(factory != null){
return factory.responseBodyConverter(type, annotations, retrofit);
}
}
//try to default to json in case no annotation on current method was found
Converter.Factory jsonFactory = mFactoryMap.get(Json.class);
if(jsonFactory != null){
return jsonFactory.responseBodyConverter(type, annotations, retrofit);
}
return null;
}
static class Builder {
Map<Class<?>, Converter.Factory> mFactoryMap = new LinkedHashMap<>();
Builder add(Class<? extends Annotation> factoryType, Converter.Factory factory){
if(factoryType == null) throw new NullPointerException("factoryType is null");
if(factory == null) throw new NullPointerException("factory is null");
mFactoryMap.put(factoryType, factory);
return this;
}
public AnnotatedConverterFactory build(){
return new AnnotatedConverterFactory(mFactoryMap);
}
}
}
用法:
interface RetrofitService{
@POST
@AnnotatedConverterFactory.Xml
Call<Envelope> someApiMethodWithXmlRespoonse(@Url String url, @Body Envelope envelope);
@POST
@AnnotatedConverterFactory.Json
Call<Envelope> someApiMethodWithJsonRespoonse(@Url String url, @Body Model model);
}
实例化改造:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(mOkHttpClient)
.addConverterFactory(new AnnotatedConverterFactory.Builder()
.add(AnnotatedConverterFactory.Xml.class, SimpleXmlConverterFactory.createNonStrict())
.add(AnnotatedConverterFactory.Json.class, GsonConverterFactory.create(mGson))
.build()
)
mRetrofitService = retrofit.create(RetrofitService.class);
对于 kotlin 用户来说要简单得多
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class XmlResponse
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class JsonResponse
class MultipleConverterFactory(private val factories: Map<Class<*>, Converter.Factory>) : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
return annotations.mapNotNull { factories[it.annotationClass.javaObjectType] }
.getOrNull(0)
?.responseBodyConverter(type, annotations, retrofit)
}
class Builder {
private val factories = hashMapOf<Class<*>, Converter.Factory>()
fun setXmlConverterFactory(converterFactory: Converter.Factory): Builder {
factories[XmlResponse::class.java] = converterFactory
return this
}
fun setJsonConverterFactory(converterFactory: Converter.Factory): Builder {
factories[JsonResponse::class.java] = converterFactory
return this
}
@Suppress("unused")
fun addCustomConverterFactory(
annotation: Class<out Annotation>,
converterFactory: Converter.Factory
): Builder {
factories[annotation] = converterFactory
return this
}
fun build(): MultipleConverterFactory {
return MultipleConverterFactory(factories)
}
}
}
和用法
Retrofit.Builder()
// ...
.addConverterFactory(
MultipleConverterFactory.Builder()
.setXmlConverterFactory(TikXmlConverterFactory.create(instance()))
.setJsonConverterFactory(GsonConverterFactory.create(instance()))
.build()
)
.build()
// some API endpoint
@XmlResponse
@GET
suspend fun getXml(
@Url url: String
): MyModel