Question

Say I have this API that:

  1. requires a lot of authentication things.
  2. always returns JSON, but sometimes an object, and sometimes an array. It's however, easy to predict what will be returned.

And I want to use this super freaking awesome Ion library (by Koushik Dutta).

As the API I'm using, requires authentication, setting proper headers with every request, etc. I'm going to wrap it somehow, say: http://goo.gl/5NLeQn

private void sendRequest(String action, JsonObject params, FutureCallback<JsonObject> callback) {

  String nonce = "" + getNonce();

  Builders.Any.B req = Ion.with(context, BASE_URL + action);

  req.setBodyParameter("key", API_KEY)
    .setBodyParameter("signature", getSignature(nonce))
    .setBodyParameter("nonce", nonce);

  if( params!=null ) {
    for(Map.Entry<String, JsonElement> param : params.entrySet()) {
      JsonElement el = param.getValue();
      if( el.isJsonPrimitive() ) req.setBodyParameter(param.getKey(), el.getAsString());
    }
  }

  req.asJsonObject()
    .setCallback(callback);
}

That works great until I need to receive a request that instead of being a JsonObject is a JsonArray:

java.lang.ClassCastException: com.google.gson.JsonArray cannot be cast to com.google.gson.JsonObject

So as a quick workaround I can create two methods that redirect flow to one common, but to make things easier here, say it looks like this: http://goo.gl/pzSal3 .

private void sendRequest(String action, JsonObject params, FutureCallback<JsonObject> callback) {

  String nonce = "" + getNonce();

  Builders.Any.B req = Ion.with(context, BASE_URL + action);

  req.setBodyParameter("key", API_KEY)
    .setBodyParameter("signature", getSignature(nonce))
    .setBodyParameter("nonce", nonce);

  if( params!=null ) {
    for(Map.Entry<String, JsonElement> param : params.entrySet()) {
      JsonElement el = param.getValue();
      if( el.isJsonPrimitive() ) req.setBodyParameter(param.getKey(), el.getAsString());
    }
  }

  req.asJsonObject()
    .setCallback(callback);
}

private void sendRequest(String action, JsonObject params, FutureCallback<JsonArray> callback) {

  String nonce = "" + getNonce();

  Builders.Any.B req = Ion.with(context, BASE_URL + action);

  req.setBodyParameter("key", API_KEY)
    .setBodyParameter("signature", getSignature(nonce))
    .setBodyParameter("nonce", nonce);

  if( params!=null ) {
    for(Map.Entry<String, JsonElement> param : params.entrySet()) {
      JsonElement el = param.getValue();
      if( el.isJsonPrimitive() ) req.setBodyParameter(param.getKey(), el.getAsString());
    }
  }

  req.asJsonArray()
    .setCallback(callback);
}

But then BAM, another error appears:

'sendRequest(String, JsonObject, FutureCallback)' clashes with 'sendRequest(String, JsonObject, FutureCallback)'; both methods have same erasure

Seems like those types got stripped down in runtime and VM gets confused.

I've come up with some "solutions":

  • I could use FutureRequest without declaring types at all, but then I need them in #18th line.
  • I could use String instead, and then parse it with Gson ,
  • I could use another param to specify type, and then cast my callback to either FutureRequest<JsonObject> or FutureRequest<JsonArray>

But all of them seem to be only some cheap hacks to make things (somehow) work. Does anyone here know any proper solution to that problem?

My most preferred way of calling those methods would be:

sendRequest(ACTION_BALANCE, null, new FutureRequest<JsonObject>() { 
    @Override
    public void onCompleted(Exception e, JsonObject result) {
        // my code goes here
    }
});

// OR just

sendRequest(ACTION_OPERATIONS, null, new FutureRequest<JsonArray>() { 
    @Override
    public void onCompleted(Exception e, JsonArray result) {
        // my code goes here
    }
});
Était-ce utile?

La solution

Instead of FutureCallback<JsonObject> and FutureCallback<JsonArray>, why not extend these like this:

public interface JsonObjectFutureCallback extends FutureCallback<JsonObject> { }
public interface JsonArrayFutureCallback extends FutureCallback<JsonArray> { }

Then you can call your methods like so:

sendRequest(ACTION_BALANCE, null, new JsonObjectFutureCallback() { 
    @Override
    public void onCompleted(Exception e, JsonObject result) {
        // my code goes here
    }
});

// OR just

sendRequest(ACTION_OPERATIONS, null, new JsonArrayFutureCallback() { 
    @Override
    public void onCompleted(Exception e, JsonArray result) {
        // my code goes here
    }
});

Autres conseils

You can use generic type to pass both FutureCallback<JsonObject> and FutureCallback<JsonArray> as a parameter to the same method.

You also need to know the type of T so you could call appropriate req.as(JsonObject/JsonArray)() method. Unfortunately java generics are mostly compile time, so the type information is lost at runtime. You can bypass that by passing extra Class parameter to your method.

So the sendRequest method should look like so:

private <T>void sendRequest(String action, JsonObject params, FutureCallback<T> callback, Class<?> type) {

    // body of the method

    if(type == JsonObject.class) {
        req.asJsonObject().setCallback(callback);
    }
    else if(type == JsonArray.class) {
        req.asJsonArray().setCallback(callback);
    }
}

And you can call your method like so:

sendRequest(ACTION_BALANCE, null, new FutureRequest<JsonObject>() { 
    @Override
    public void onCompleted(Exception e, JsonObject result) {
       // my code goes here
    }
}, JsonObject.class);

// OR just

sendRequest(ACTION_OPERATIONS, null, new FutureRequest<JsonArray>() { 
    @Override
    public void onCompleted(Exception e, JsonArray result) {
        // my code goes here
    }
}, JsonArray.class);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top