3

I am trying to make the three following methods into one generic solution, I tried some ideas which compile but don't do well at runtime.

public static List<User> parseToUsers(HttpResponse response) {
  ObjectMapper mapper = new ObjectMapper();
  String results = parseToString(response);
  return mapper.readValue(results, new TypeReference<List<User>>() {});
}

public static List<Record> parseToRecords(HttpResponse response) {
  ObjectMapper mapper = new ObjectMapper();
  String results = parseToString(response);
  return mapper.readValue(results, new TypeReference<List<Record>>() {});
}

public static Record parseToRecord(HttpResponse response) {
  ObjectMapper mapper = new ObjectMapper();
  String results = parseToString(response);
  return mapper.readValue(results, new TypeReference<Record>() {});;
}

I have also tried to understand this blog post about Super Type Tokens.

EDIT:

This is what I came up with so far:

public static <T> T parseJsonResponse(TypeReference<T> type, HttpResponse response) throws DroidException {
    ObjectMapper mapper = new ObjectMapper();
    String results = parseResponseToString(response);
    return readValue = mapper.readValue(results, type);
}

Then I call it like this.

parseJsonResponseToList(new TypeReference<List<Record>>() {}, response)

Not really satisfieng.Is there a better solution?

Paul Bellora
  • 51,514
  • 17
  • 127
  • 176

4 Answers4

2

So what exactly is the problem? In what way do you not like it?

Jackson has other ways for constructing generic types; so perhaps what are looking for is along lines of:

public List<T> listOf(String json, Class<T> elementType) {
   ObjectMapper mapper = new ObjectMapper(); // should (re)use static instance for perf!
   JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, elementType);
   return mapper.readValue(json, listType);
}

TypeFactory can be used to programmatically construct types that use generics -- return type is JavaType, because basic Java Class is type-erased. TypeFactory is actually used to convert TypeReference to JavaType internally as well.

EDIT

As to regular, non-Collection/Map types, it's really quite simple:

public T parseSingle(Class<T> cls, InputStream src) throws IOException {
  return mapper.readValue(src, cls);
}

(you also do NOT want to read contents as String -- not only is it slow, but it's easy to mess up character encodings, so if possible, feed InputStream or byte[] instead)

StaxMan
  • 102,903
  • 28
  • 190
  • 229
  • yeah im pretty confused by the jackson lib anyways :P, so ObjectMapper is thread safe? the use case we have here is that we also want to be able to parse to a single User instead of a List –  Mar 09 '12 at 03:00
  • Have you read the FAQ (http://wiki.fasterxml.com/JacksonFAQ)? Yes, it is thread-safe (first hit with google "jackson thread safe", FWIW). – StaxMan Mar 09 '12 at 18:46
  • As to regular vs List: for non-Collection types, you need not bother with either `TypeReference` of `TypeFactory`: just pass the Class and it will "just work" – StaxMan Mar 09 '12 at 18:47
  • i just looked at the api documentation and i couldnt read threadsafe there. I have the String return type because i want to make sure others dont forget to consume the HttpResponse. So i wrapped all the access to Http Layer. But i checked and i guess i can make the methods return byte[] instead and since String Constructor takes byte[] as an argument it should work out. thanks alot! –  Mar 09 '12 at 22:11
  • Hmm but then it would be easier to mess up character encoding because if someone uses the String Constructor without charset parameter... Is the performance increase significant? I mean in the end im doing the transformation on the client. So i think making sure the encoding isnt messed up is more important? what do you think? –  Mar 09 '12 at 22:14
  • Yes, never ever construct String without specifying encoding explicitly. But why are you constructing a String? For data-binding, using `byte[]` is better anyway, and `ObjectMapper` is happy to take one (InputStream, Reader, URL, String are all fine too) – StaxMan Mar 12 '12 at 18:32
  • 1
    ty, just refactored my http wrapper, constructing strings was a bad design decision :) so im passing an inputstream to the readValue method now. how does objectmapper know which charset the inputstream has? im a lil bit confused now if there might come up a charset issue. –  Mar 20 '12 at 19:45
  • It can reliably auto-detect it, using first couple of bytes, as per JSON specification -- there are only 3 legal encodings (UTF-8 / 16 / 32) to choose from. – StaxMan Mar 21 '12 at 17:39
0

I don't really know what your ObjectMapper and TypeReference classes do, so maybe this answer doesn't fit you all that well, but here's how I'd probably do it if I understand your situation at all:

public interface Parser<T> {
    public T parse(String results);
    public static class MapperParser<T> implements Parser<T> {
        private final TypeReference<T> type;
        public MapperParser(TypeReference<T> type) {this.type = type;}
        public T parse(String results) {
            return(new ObjectMapper().readValue(results, type));
        }
    }
    public static final Parser<List<User>> users = new MapperParser(new TypeReference<List<User>>());
    public static final Parser<List<Record>> records = new MapperParser(new TypeReference<List<Record>>());
    public static final Parser<Record> record = new MapperParser(new TypeReference<Record>());
}

/* And then, in the class you were in your question: */
public static <T> T parseJsonResponse(Parser<T> parser, HttpResponse response) {
    return(parser.parse(parseResponseToString(response)));
}

Then, you may call it as such:

parseJsonResponse(Parser.users, response)

Is that more to your liking?

Dolda2000
  • 23,371
  • 2
  • 45
  • 83
  • that looks quite nice and is good inspiration but too much code for my taste. –  Mar 08 '12 at 15:36
0

Ok this is my favorite solution, inspired by Dolda2000, i keep as is in my initial post and add an enum.

public enum TypeRef {
    RECORDS(new TypeReference<List<Record>>() {}), USERS(new TypeReference<List<User>>() {}), USER(new TypeReference<User>() {});

    private TypeReference<?>    type;

    private TypeRef(TypeReference<?> type) {
        this.type = type;
    }

    public TypeReference<?> getType() {
        return this.type;
    }
}

and then instead of writing:

readJsonResponse(new TypeReference<List<Record>>() {}, response)

i can write:

readJsonResponse(TypeRef.RECORDS, response);

no magic going on but i like it more than wrapping it in another interface

0

Ok after running into a compile error:

type parameters of T cannot be determined; no unique maximal instance exists for type variable T with upper bounds T,java.lang.Object

i quit the over engeneering session and keep it simple

private static TypeReference<List<Record>>  RECORDS = new TypeReference<List<Record>>() {};

public static <T> T readJson(TypeReference<T> type, String text) {
    ObjectMapper mapper = new ObjectMapper();
    return readValue = mapper.readValue(text, type);
}

use it like this

readJson(RECORDS, text);

no enums, i just use static fields for the TypeReference and everyone can read the code easily without understanding TypeReference

thank you guys i learned something about over engeneering today :P