Search code examples
javaandroidapigsonion-koush

Detecting argument type in given FutureRequest


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
    }
});

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
        }
    });