1

I want a method that extracts the data from a JSON-object parsed before as the correct type. The JSONObject (rawdata) extends Map, so it looks like this:

private <Type> Type getValue(String key, Type def)
{
    if (!rawdata.containsKey(key)) return def;
    if (!(rawdata.get(key) instanceof Type)) return def;
    return (Type) rawdata.get(key);
}

The instanceof obviously generates a compile-time-error. The parameter def is the default-value, returned if the key is not available or has the wrong type. But def can also be null, so def.getClass() isn't working.

Any ideas how I can check the content of the Map-entry for the correct type?

Mnementh
  • 50,487
  • 48
  • 148
  • 202
  • possible duplicate of [How to determine the class of a generic type?](http://stackoverflow.com/questions/182636/how-to-determine-the-class-of-a-generic-type) – Nicolas Mar 26 '12 at 09:24
  • I think you accidentally - an extra 'Type' in method signature. – questzen Mar 26 '12 at 09:24
  • @questzen no, it is a generic declaration, but it would have been more readable using standard conventions: `private T getValue(String key, T def)` – assylias Mar 26 '12 at 09:30
  • You could have defined `rawdata` so that failing the `instanceof` test was in principle impossible. You haven't stated why you haven't done that, or even provided the definition of `rawdata`. Hard to answer without more info. – user207421 Mar 26 '12 at 09:45
  • @EJP: I use a library for parsing JSON. It parses the data into a structure, that is basically a Map. As JSON-Values can be Numbers, Strings, more JSON-Object and Arrays this Map is untyped. rawdata is of that JSONObject-class. – Mnementh Mar 26 '12 at 09:47

4 Answers4

8

Due to type erasure, the only way to handle the case where the default value can be null is to have the method require an additional parmeter of type Class - which is generally better because it allows the default value to be a subclass of the required type.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
2

You just need to check for nulls (a null will count as any type, if this is undesired behaviour then you will also need to pass in the desired class).

private <T> T getValue(String key, T def)
{
    if (!rawdata.containsKey(key)) return def;

    Object value = rawdata.get(key);

    if (def == null) return (T) value; 
    // note that the above is inherently unsafe as we cannot 
    // guarantee that value is of type T

    // this if statement is the same as "value instanceOf Type"
    // is type safe, but not null safe
    if (def.getClass().isAssignableFrom(value.getClass())) {
        return (T) value;
    } else {
        return def;
    }
}

A safer method signature would be:

private <T> T getValue(String key, T defaultValue, Class<T> defaultClass)

This way we can safely check the types match even when the default is null.

Dunes
  • 37,291
  • 7
  • 81
  • 97
1

Your best bet is probably to accept a Class object for the return type in the case when no default value is given. You can just overload the function like:

private <T> T getValue(String key, Type defaultValue);

private <T> T getValue(String key, Class<T> type);
takteek
  • 7,020
  • 2
  • 39
  • 70
0

Or you could use the Typesafe Heterogeneous Container (THC) pattern from "Effective Java Second Edition" book by Joshua Bloch.

Basically, store the item's Class in the map when inserting. When retrieving you'll know the type is the same.

Map<Class, Map<String, Object>> rawData = ...
Andrejs
  • 26,885
  • 12
  • 107
  • 96