针对未知根元素的改造和SimpleXML?

时间:2015-12-02 15:45:30

标签: android xml retrofit simple-framework

我们目前正在为XML API设置Retrofit - 遗憾的是每个请求都可以使用两个不同的根元素之一返回响应

通常情况下,每个响应都是这样的(当然,<response>标记中包含的实际元素因每个请求而异):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <SomeInfo>Some Info</SomeInfo>
    <MoreInfo>More Info</MoreInfo>
</response>

每个错误都是这样的(每个响应的结构都相同):

<?xml version="1.0" encoding="UTF-8"?>
<error>
    <code>125002</code>
    <message></message>
</error>

现在,到目前为止我们发现的唯一方式是以一种通用的方式完成这项工作:

public interface Api {

    @GET("/api/sessionToken")
    Observable<ResponseBody> requestSessionToken();

    @GET("/api/pinStatus")
    Observable<ResponseBody> requestPinStatus();
}

public class RestClient {

    public RestClient() {
        // ...
        mApiService = retrofit.create(Api.class);
    }

    public Observable<PinStatusResponse> requestPinStatus() {
        return mApiService.requestPinStatus()
                .flatMap(foo(PinStatusResponse.class, PinStatusResponseData.class));
    }

    public Observable<SessionTokenResponse> requestSessionToken() {
        return mApiService.requestSessionToken()
                .flatMap(foo(SessionTokenResponse.class, SessionTokenResponseData.class));
    }

    private final <O extends MyResponse, I> Func1<ResponseBody, Observable<T>> foo(final Class<O> outerCls, final Class<I> innerCls) {
        return new Func1<ResponseBody, Observable<O>>() {
            @Override
            public Observable<O> call(ResponseBody responseBody) {
                try {
                    final String xmlString = responseBody.string();

                    final XmlPullParser parser = Xml.newPullParser();
                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
                    parser.setInput(new ByteArrayInputStream(xmlString.getBytes(Charset.forName("UTF-8"))), null);
                    parser.nextTag();

                    final String rootTag = parser.getName();

                    final Serializer serializer = new Persister();

                    if (TextUtils.equals(rootTag, "error")) {
                        final MyError myError = serializer.read(MyError.class, xmlString);
                        return Observable.just((O) outerCls.getConstructor(MyError.class, innerCls).newInstance(myError, null));
                    } else if (TextUtils.equals(rootTag, "response")) {
                        final I data = serializer.read(innerCls, xmlString);
                        return Observable.just((T) outerCls.getConstructor(MyError.class, innerCls).newInstance(null, data));
                    }
                } catch (XmlPullParserException e) {
                    return Observable.error(e);
                } catch (IOException e) {
                    return Observable.error(e);
                } catch (Exception e) {
                    return Observable.error(e);
                }
                return Observable.error(new Exception("Should not be reached..."));
            }
        };
    }
​}

Response类看起来像这样:

public abstract class MyResponse<T> {
    public final MyError error;
    public final T data;

    protected MyResponse(MyError error, T data) {
        this.error = error;
        this.data = data;
    }
}

public final class PinStatusResponse extends MyResponse<PinStatusResponseData> {
    public PinStatusResponse(MyError error, PinStatusResponseData data) {
        super(error, data);
    }
}

并且所有*Data类都直接对应于(非错误)XML响应。

现在,我的问题是:是否真的没有更简单的方法来解决这个问题?(如果是这样,这是否是API设计错误的迹象?)。

1 个答案:

答案 0 :(得分:0)

这是@ElementUnion注释的用途。您可以通过使用带注释的对象使用纯Retrofit + SimpleXML API来解决这个问题:

@Root
public class ResponseBody {

    public interface IApiResponse {
    }

    @Root
    public static class ValidResponse implements IApiResponse {
        @Element(name="SomeInfo") String someInfo;
        @Element(name="MoreInfo") String moreInfo;
    }

    @Root
    public static class ErrorResponse implements IApiResponse {
        @Element(name="code") int code;
        @Element(name="message") String message;
    }

    @ElementUnion({
        @Element(name="response", type=ValidResponse.class),
        @Element(name="error", type=ErrorResponse.class)
    })
    IApiResponse apiResponse;
}

*&#39; ValidResponse&#39;的具体结构和&#39; ErrorReponse&#39;必须根据您拥有的真实XML结构进行更改。您可能还希望考虑在'strict=false'中添加@Root

至于你的Api&#39;界面,它必须看起来像这样(注意我已经在使用Retrofit的Call类中):

public interface Api {

    @GET("/api/sessionToken")
    Call<ResponseBody> requestSessionToken();

    @GET("/api/pinStatus")
    Call<ResponseBody> requestPinStatus();
}

最后,调用本身(例如requestPinStatus())应该根据这个骨架实现来解决:

Call<ResponseBody> result = mApiService.requestPinStatus();
result.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
        // ...

        if (response.body().apiResponse instanceof ValidResponse) {
            // ...
        } else if (response.body().apiResponse instanceof ErrorResponse) {
            // ...
        }
    }

    @Override
    public void onFailure(Throwable t) {
        // ...
    }
});

有关ElementUnion和Simple-XML注释的详细信息,请参阅this guide