Search code examples
androidxmlretrofit2simple-xml-converter

Add root single element to xml using Retrofit 2


I have a server response in xml, that is not well formatted and have no root element:

<option value="stationAValue">stationADescription</option>
<option value="stationBValue">stationBDescription</option>

I trying to use SimpleXmlConverterFactory like this:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(Params.BASE_URL)
    .client(okHttpClient)
    .addConverterFactory(SimpleXmlConverterFactory.create())
    .build();

This is my class that represents a row:

public class Station {
    @Element(name = "option")
    private String mName;

   @Attribute(required = false)
   private String value;
}

But of course it can't be parsed without a root element, Is there a way to manipulate the response before the SimpleXmlConverterFactory is trying to parse it, and add a root element?

Or maybe another solution?


Solution

  • With Retrofit / OkHttp you have 2 options for intercepting those requests:

    • Use interceptors with OkHttp and modify the responses / requests directly
    • Wrap the parser and modify the response before passing it in

    Both is somewhat a decorator pattern.

    Using an interceptor

    Modify the response as part of the http stack directly:

    public class XmlInterceptor implements Interceptor {
      @Override
      public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        ResponseBody body = response.body();
        String wrappedBody = "<root>" + body.string() + "</root>";
        return response.newBuilder()
                .body(ResponseBody.create(body.contentType(), wrappedBody))
                .build();
      }
    }
    

    And just add the interceptor to OkHttp

    new OkHttpClient.Builder()
        .addInterceptor(new XmlInterceptor())
        .build();
    

    Using a wrapped parser

    Wrap the parser you want to use and again just modify the response. The nice thing here is, you could add a custom annotation for your wrapping. e.g. to pass in the name of the root element.

    public class XmlParser extends Converter.Factory {
    
      private Converter.Factory factory = SimpleXmlConverterFactory.create();
    
      @Override
      public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        // here you can actually look at the annotations, type, etc.
        return new WrappedResponseBodyConverter(factory.responseBodyConverter(type, annotations, retrofit));
      }
    
      private class WrappedResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        private Converter<ResponseBody, T> responseBodyConverter;
    
        public WrappedResponseBodyConverter(Converter<ResponseBody, T> responseBodyConverter) {
          this.responseBodyConverter = responseBodyConverter;
        }
    
        @Override
        public T convert(ResponseBody value) throws IOException {
          String body = "<root>" + value.string() + "</root>";
          ResponseBody wrapped = ResponseBody.create(value.contentType(), body);
          return responseBodyConverter.convert(value);
        }
      }
    }
    

    And use this one instead.

    new Retrofit.Builder()
        .addConverterFactory(new XmlParser())
        .build();
    

    Choose whichever you prefer, as there is no right or wrong imho.


    The code is not tested. It's just an example.