1

Based on this question How to get a class instance of generics type T I have implemented the following class:

public class OkJsonConverter<T> {

    final Class<T> typeParameterClass;

    public OkJsonConverter(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    protected T processJson(String json) throws OkClientException {
        T object = null;
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            JsonNode jsonNode = objectMapper.readTree(json);

            if (jsonNode.get("error_code") != null) {
                Error error = objectMapper.treeToValue(jsonNode, Error.class);
                throw new OkClientException("API returned error", error);
            } else {
                object = objectMapper.treeToValue(jsonNode, typeParameterClass);
            }
        } catch (IOException e) {
            throw new OkClientException("unable to process json", e);
        }
        return object;
    }

}

I can use this class with a generic parameters, for example:

return new OkJsonConverter<User>(User.class).processJson(response.getBody());

but right now I'm struggling how to make it working with a nested generic parameter like this one List<Method>

This code doesn't work:

 return new OkJsonConverter<List<Method>>(List<Method>.class).processJson(response.getBody());

Please help to change this code in order to get it working.

Community
  • 1
  • 1
alexanoid
  • 24,051
  • 54
  • 210
  • 410

2 Answers2

4

Java doesn't have any way to represent that type as a Class. The closest approximation you can get is (Class<List<Method>>) (Class) List.class, but that cast just papers over that you're just looking at a basic List that doesn't know its element type.

Whether or not that works with your JSON converter isn't clear, but should be specified in the documentation of the converter you're using, which will have to deal with this itself, since this is a universal problem in Java when you're trying to reflect on generic types.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • Thanks for your answer. I'm trying to apply your advice to my code but without any success right now.. – alexanoid Jan 07 '16 at 23:27
  • 2
    Java has a [way](http://gafter.blogspot.com/2006/12/super-type-tokens.html) to represent parameterized types, [Jackson](https://github.com/FasterXML/jackson-core) for example [uses this technique](https://fasterxml.github.io/jackson-core/javadoc/2.4/com/fasterxml/jackson/core/type/TypeReference.html). – user3707125 Jan 08 '16 at 00:02
  • 1
    @user3707125, yes, that's true; that's why I clarified "any way to represent that type as a `Class`." – Louis Wasserman Jan 08 '16 at 00:02
  • 2
    Agree, but he didn't ask how to represent the type as a `Class` - he asked how to pass generic type information. And you didn't mention in your answer that there is an existing solution that is supported by major serialization/deserialization libraries, while this may have shown the author a way to solve his issue. – user3707125 Jan 08 '16 at 00:10
  • 1
    @user3707125 I got the impression they were using an external JSON serialization tool, which I couldn't find on Google, and couldn't tell if it accepted anything like TypeToken. I recommended checking its documentation, which I figured would mention a TypeToken-like thing if it supported it at all, or some alternate way of working around the issue. – Louis Wasserman Jan 08 '16 at 00:12
  • Well he is obviously using Jackson itself, see [ObjectMapper.treeToValue()](https://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#treeToValue%28com.fasterxml.jackson.core.TreeNode,%20java.lang.Class%29). I also edited the question to add the tag. – Didier L Jan 09 '16 at 14:22
0

Finally, thanks to user3707125 I have found a way how to implement this:

Corrected by idierL

public class OkJsonConverter {

private static final String ERROR_CODE_FIELD_NAME = "error_code";

protected <T> T readTreeToValue(String json, TypeReference<T> valueTypeRef) throws OkClientException {
    T object = null;

    ObjectMapper objectMapper = new ObjectMapper();
    try {
        JsonNode jsonNode = objectMapper.readTree(json);

        if (jsonNode.has(ERROR_CODE_FIELD_NAME)) {
            Error error = objectMapper.treeToValue(jsonNode, Error.class);
            throw new OkClientException("Ok API returned error", error);
        } else {
            JavaType type = objectMapper.getTypeFactory().constructType(valueTypeRef);
            object = objectMapper.convertValue(jsonNode, type);
        }
    } catch (IOException e) {
        throw new OkClientException("Unable to process JSON", e);
    }

    return object;
}

}

Now, the following code works fine:

List<Method> result = new OkJsonConverter().readTreeToValue(response.getBody(), new TypeReference<List<Method>>() {});
Community
  • 1
  • 1
alexanoid
  • 24,051
  • 54
  • 210
  • 410
  • Note that your solution is not type safe at all (as indicated by your `@SuppressWarnings`): this will work even if the JSON actually contains a `List`, and you will only notice it at runtime when you try to operate on `result`, when you'll get a `ClassCastException`. As a general rule, avoid such suppress warnings at all costs, as you are almost guaranteed to run into trouble at some point. – Didier L Jan 09 '16 at 14:05
  • Thanks for your comment. Unfortunately right now I don't have a better one – alexanoid Jan 09 '16 at 14:07
  • Actually, there is one… Just replace the unchecked cast and the `treeToValue` call by `objectMapper.convertValue(jsonNode, type)`. – Didier L Jan 09 '16 at 14:15