Unfortunately, Date and Integer values ββare deserialized as String and Double.
When you define a generic field of type Field<T> , Gson may not have enough information about how a particular value should be deserialized to a specific type. This is a fundamental limitation: no type information. Therefore, Gson resolves <T> as if it were parameterized as <Object> . When a specific target slot (list item, object field, etc.) is considered java.lang.Object , Gson resolves the JSON value according to the type of the literal of the value: if it is something like "..." then it is probably , a String ; if it is something like 0 , then it is definitely a Number and more accurate: Double (doubles the largest standard numeric values ββ- Gson just saves time on finding the type of number and parsing + the user code should have java.util.List<Number> and Define a specific list item using instanceof - it can be an integer, long or double value, which is not very convenient for use, therefore java.lang.Double is the default strategy). So you have strings and doubles instead of dates and integers: Gson simple cannot have the desired type information.
Why you cannot use type tokens directly: type markers are used to indicate type parameters for elements of the same type, so you cannot have several type tokens for different types even for a two-element list (list token types determine the type for all list elements).
To accomplish what you need, you can create a type adapter and an appropriate factory type adapter to do some kind of search for a particular type of solution. Let's say
final class FilterTypeAdapterFactory implements TypeAdapterFactory {
OK, how can I use it? I checked it with the following mappings:
final class Filter<T> { final String fieldName = null; final String operand = null; final T value = null; }
final class FilterInfo { final List<Filter<?>> filters = null; final String orderBy = null; }
In-JSON Type Name Strategy
If you can specify type names in your JSON to search for a filter type, then a JSON pattern might look like this:
{ "filters": [ {"_type": "date", "fieldName": "fooDate", "operand": "=", "value": "1997-12-20"}, {"_type": "int", "fieldName": "barInteger", "operand": ">=", "value": 10} ], "orderBy": "fooDate" }
Now the Gson instance can be created as follows:
private static final Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd") .registerTypeAdapterFactory(getFilterTypeAdapterFactory(jsonObject -> { if ( !jsonObject.has("_type") ) { return defaultFilterType; } switch ( jsonObject.get("_type").getAsString() ) { case "int": return integerFilterType; case "date": return dateFilterType; default: return defaultFilterType; } })) .create();
Alternative strategy
If you donβt want to improve your JSON documents (good at the same time), then you can simply replace the strategy, however, resolving types can be more complicated due to several reasons, since it depends heavily on the given filter value names (the same name can be used for different types):
{ "filters": [ {"fieldName": "fooDate", "operand": "=", "value": "1997-12-20"}, {"fieldName": "barInteger", "operand": ">=", "value": 10} ], "orderBy": "fooDate" }
private static final Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd") .registerTypeAdapterFactory(getFilterTypeAdapterFactory(jsonObject -> { if ( !jsonObject.has("fieldName") ) { return defaultFilterType; } switch ( jsonObject.get("fieldName").getAsString() ) { case "barInteger": return integerFilterType; case "fooDate": return dateFilterType; default: return defaultFilterType; } })) .create();
Please note that TypeToken and Type can be considered immutable and constant, so they can be placed in a separate class:
final class Types { private Types() { } static final Type defaultFilterType = new TypeToken<Filter<Object>>() { }.getType(); static final Type integerFilterType = new TypeToken<Filter<Integer>>() { }.getType(); static final Type dateFilterType = new TypeToken<Filter<Date>>() { }.getType(); }
Now, for both styles, the following code
final FilterInfo filterInfo = gson.fromJson(JSON, FilterInfo.class); System.out.println(filterInfo.orderBy); for ( final Filter filter : filterInfo.filters ) { System.out.println(filter.fieldName + filter.operand + filter.value + " of " + filter.value.getClass()); }
will output:
fooDate
fooDate = Sat Dec 20 00:00:00 EET 1997 class java.util.Date
barInteger> = 10 of class java.lang.Integer