David
David

Reputation: 37556

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?

Upvotes: 0

Views: 1610

Answers (2)

David Medenjak
David Medenjak

Reputation: 34542

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.

Upvotes: 5

Gordon developer
Gordon developer

Reputation: 417

I have done something almost similar to your current ptoblem. Once you receive the xml response in java, you can use + operation to include you root element, like so :

String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<root>" + response + "</root>";

here, response is the xml response you get you get in String format. Note that you can also manipulate the xml respose to suit your needs by treating it as a string, so you can concat any additional data into your response and you can also split the response at different parts to suit the format that you require. Hope this will assist.

Upvotes: -1

Related Questions