4

How to do the following properly in Java? I would like to have a generic routine that can create a list of objects. In this routine I expect the constructor of the class of these objects to support a particular parameter.

To clarify: I would like this routine to create a List<T> from a JSON string. This is part of a larger deserialization code. If I can somehow specify that each supported T implements a constructor that creates T given a JSONObject, then I could write the routine something like this:

interface CreatableFromJSONObject<T> {
    T(JSONObject object);     // Complains about missing return type.
} 

static <T> List<T extends CreatableFromJSONObject> jsonArrayToList(JSONArray array) {
    List<T> result = new ArrayList<T>();
    for (int i = 0; i < array.length; ++i) {
         JSONObject jsonObject = array.getJSONObject(i);
         result.add(T(jsonObject));    // If T has one constructor with 1 one argument JSONObject
    }
    return result;
}

and then with an example class that implements this interface

class SomeClass implements CreatableFromJSONObject {
    SomeClass(JSONObject object) throws JSONException {
        // implementation here
    }
}

I could use the desired method as:

List<SomeClass> list = jsonArrayToList<SomeClass>(someJSONArray);

Now, there are quite some hits on this on StackOverflow, so there I have learned that what I outlined above is not possible because Java does not support specifying a particular constructor in an interface and also not for static methods (which would be an alternative route to the same thing and not possible for the same reason).

So, what is then the best way of achieving this?

My current best attempt is the following:

public static <T> List<T> jsonArrayToList(final JSONArray jsonArray, Constructor<T> fromJSONObjectConstructor) {
    List<T> result = new ArrayList<T>();
    try {
        for (int i = 0; i < jsonArray.length(); i++) {
            result.add(fromJSONObjectConstructor.newInstance(jsonArray.getJSONObject(i)));
        }
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (JSONException e) {
        throw new RuntimeException(e);
    }
    return result;
}

and then to add to each class that should be supported by this method:

public class SomeClass {
    public static final Constructor<SomeClass> jsonObjectConstructor;
    static {
        try {
            jsonObjectConstructor = CafellowEntity.class.getConstructor(JSONObject.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    SomeClass(JSONObject object) throws JSONException {
        // Implementation here
    }
}

which I use as

List<SomeClass> list = jsonArrayToList(myJSONArray, SomeClass.jsonObjectConstructor);

It is the most pretty solution I could come up with apart from not using a generic implementation at all and just put the (in this case) couple of lines of code that is actually doing the work in the routine wherever I need them for a particular class.

Any suggestions? What about the performance of this versus alternative solutions? By not supporting it just like this Java is probably telling me I should not be wanting to do this, but that does not prevent me from wondering about it anyway.

Community
  • 1
  • 1
Martijn de Milliano
  • 3,908
  • 3
  • 36
  • 45
  • What are you ***really*** trying to accomplish? Are you doing some custom deserialization in these types you are passing around? – Perception Feb 25 '13 at 22:16
  • Yes, this is part of code that does JSON deserialization. In the constructor of a specific class I read the `JSONObject` and set the fields of the new instance using its contents. This is common for many of the classes I use, and I would like to have this generic method for deserializing a JSON array. – Martijn de Milliano Feb 25 '13 at 22:19
  • Just a side note: You might what to look into the Java `jackson` library. http://jackson.codehaus.org/ It does basically all the serialization/deserialization for you. – Simon Forsberg Feb 28 '13 at 16:40

2 Answers2

4

Unless you are doing some kind of unusual deserialization this design is overly-complicated and error prone. Android bundles an excellent JSON parser that can already do this, and do it well. Each type that you are currently defining custom constructors for can be deserialized with a single line of code:

final CustomObj obj = new Gson().fromJson(jsonObj.toString(), CustomObj.class);

Incorporating this into your existing method, you end up with this:

public static <T> List<T> jsonArrayToList(final JSONArray jsonArray,
        Class<T> clazz) {
    if (jsonArray == null || clazz == null)
        return null;

    final Gson gson = new Gson();
    List<T> result = new ArrayList<T>(jsonArray.length());
    for (int i = 0; i < jsonArray.length(); i++) {
        try {
            result.add(gson.fromJson(jsonArray.getJSONObject(i).toString(),
                    clazz));
        } catch (final JSONException je) {
            je.printStackTrace();
            return null;
        }
    }

    return result;
}
Perception
  • 79,279
  • 19
  • 185
  • 195
  • Thanks for introducing me to GSON, it looks like a nice library that removes the need for this function of mine altogether! It does not come with Android apparently, but is easy enough to add. I also just read that it is possible to specify custom naming conventions (so you could if you wanted to decouple the field names in JSON from member names), but you are right that all this is overly complicated. – Martijn de Milliano Feb 26 '13 at 21:18
  • @MartijndeMilliano - Android comes bundled with both GSON and Simple JSON, out of the box. – Perception Feb 26 '13 at 21:37
0

You may simplify your solution by omitting the constructors instances and using the clazz instances instead. So the signature of jsonArrayToList() would be:

public static <T> List<T> jsonArrayToList(final JSONArray jsonArray, Class<T> clazz); 

and then calling the constructor by clazz.newInstance();

This would work if you know the type of the class in advance.

Jiri Kremser
  • 12,471
  • 7
  • 45
  • 72
  • This certainly cleans up the ugly constructor reference, but I don't seem to be able to pass my `JSONObject` as argument to `newInstance`. So where do I put the class-specific deserialization code? – Martijn de Milliano Feb 26 '13 at 21:04
  • @MartijndeMilliano Jiri is missing something here in the answer. First you need to call `constructor = clazz.getConstructor(JSONObject.class);`, and then you can use `constructor.newInstance(object);`. – Simon Forsberg Feb 28 '13 at 16:43
  • @SimonAndréForsberg thanks for the clarification! I accept this answer because it is closer to the question and it uses Android's built-in JSON serialization/deserialization; for a new project I'll definitely investigate Jackson and Gson. – Martijn de Milliano Feb 28 '13 at 19:45
  • @MartijndeMilliano Jiris answer, which is mostly based on the code in your question, does **not** use the built-in JSON serialization in Android. The answer by Perception does. I highly recommend that answer instead. – Simon Forsberg Mar 01 '13 at 10:25
  • 1
    @MartijndeMilliano It is also defined directly on the Class class (see http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()) My answer was general, not Android SDK speficic. If there is any more convenient api, don't reinvent the wheel and go for it :] – Jiri Kremser Mar 01 '13 at 12:17