gson.fromJson(data, new TreeMap<>(...).getClass())
does not make much sense since getClass()
only returns the object class, and not its internal state -- simply speaking, you always get TreeMap.class
regardless the arguments passed to the TreeMap
constructor.
To me, there are two options. They two use "normalized" maps and have their own advantages and disadvantages.
First off, let's create the commons class in order to share the basics between two approaches demos:
Commons.java
final class Commons {
private Commons() {
}
static final String CTP = "ctp";
static final String TYPE = "type";
static final String XID = "xid";
static final String JSON_1 = "{\"xid\":\"foo\",\"type\":\"pageview\",\"ctp\":\"ctp\"}";
static final String JSON_2 = "{\"xID\":\"foo\",\"tYPE\":\"pageview\",\"cTP\":\"ctp\"}";
static Map<String, Object> normalizeMap(final Map<String, Object> map) {
final Map<String, Object> normalized = new TreeMap<>(CASE_INSENSITIVE_ORDER);
normalized.putAll(map);
return normalized;
}
}
CaseInsensitive1.java
This option does not touch the original deserialization strategy, so the consuming code must check against case-insensitivity itself. It's necessary to put the "map normalization" code where it makes sense. Note that the original map remains unchanged.
public final class CaseInsensitive1 {
private CaseInsensitive1() {
}
public static void main(final String... args) {
final Gson gson = new Gson();
@SuppressWarnings("unchecked")
final Map<String, Object> map1 = gson.fromJson(JSON_1, Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> map2 = gson.fromJson(JSON_2, Map.class);
out.println(isTupleValid(map1));
out.println(isTupleValid(map2));
}
private static boolean isTupleValid(final Map<String, Object> map) {
if ( map == null ) {
return false;
}
final Map<String, Object> ciMap = normalizeMap(map);
return ciMap.containsKey(TYPE)
&& ciMap.containsKey(XID)
&& ciMap.containsKey(CTP)
&& ciMap.get(TYPE).equals("pageview");
}
}
CaseInsensitive2.java
This option creates a special GSON instance that deserializes Map<String, Object>
in a "normalized" way. Note that mapStringObjectType
is used everywhere since I mapped the deserialization strategy for that concrete type. It might be mapped just to Map.class
of course. The decorateGson
takes another GSON instance in order to "inherit" the previosly built behavior you might need. Despite the isTupleValid
method is now equivalent to yours, a disadvantage of this option is that you lose the properties order of the original JSON object. However, you might want to use case-insensitive maps not based on TreeMap (not sure, but it could make the map behave in case-insensitive way keeping the original properties order).
public final class CaseInsensitive2 {
private CaseInsensitive2() {
}
private static final Type mapStringObjectType = new TypeToken<Map<String, Object>>() {
}.getType();
public static void main(final String... args) {
final Gson originalGson = new Gson();
final Gson gson = decorateGson(originalGson);
@SuppressWarnings("unchecked")
final Map<String, Object> map1 = gson.fromJson(JSON_1, mapStringObjectType);
@SuppressWarnings("unchecked")
final Map<String, Object> map2 = gson.fromJson(JSON_2, mapStringObjectType);
out.println(isTupleValid(map1));
out.println(isTupleValid(map2));
}
private static boolean isTupleValid(final Map<String, Object> map) {
return map != null
&& map.containsKey(TYPE)
&& map.containsKey(XID)
&& map.containsKey(CTP)
&& map.get(TYPE).equals("pageview");
}
private static Gson decorateGson(final Gson originalGson) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<Map<String, Object>> adapter = (TypeAdapter) originalGson.getAdapter(Map.class);
final JsonDeserializer<Map<String, Object>> typeAdapter = (json, type, context) -> normalizeMap(adapter.fromJsonTree(json));
return new GsonBuilder()
.registerTypeAdapter(mapStringObjectType, typeAdapter)
.create();
}
}
To me, the option #1 looks better, since it does not decorate the originally deserialized object in any way (what if I need it to be case-sensitive later in the code?) and the case-insensitive check is performed only when it's really needed.