One of the fields in my JSON response is a String[] when containing more than one element, and a String when it's just one. Like this:
"assets": [
"0901d196804adc1c",
"0901d196804ebd93",
"0901d196804ea5e2"
]
"assets": "0901d196804adc1c"
Ideally, I would like to get a String[] always, so if the JSON type of the element is String, convert it to a String[] with one element.
How can I do that?
If you cannot edit the response on the server side: Please refer to this question and answers, looks quite similar to your situation.
If you can edit the response, just reply always with String arrays (f.e. "assets": ["0901d196804adc1c"]).
You have two options:
1) Implement custom type adapter for gson to handle such situations (preferable solution).
2) Define field of type Object and cast it to the appropriate type at runtime
public static class AssetsContainer{
private Object assets;
public List<String> getAssets() {
if(assets instanceof List<?>) {
return (List<String>) assets;
} else if(assets instanceof String){
return Arrays.asList((String) assets);
} else {
//TODO: handle
return null;
}
}
}
How about using the TypeAdapter API?
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(SingletonListTypeAdapter.FACTORY)
.create();
The following will check for non-array JSON types when expecting array types and try to make them into singleton Java Lists. (Note that this uses Lists, not arrays. You can adapt it if you want, but Effective Java notes that application-layer code should prefer the Collections APIs over arrays.)
final class SingletonListTypeAdapter<T> extends TypeAdapter<List<T>> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
#SuppressWarnings("unchecked")
#Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != List.class) {
return null;
}
TypeToken<?> collectionElementType = TypeToken.get(
getCollectionElementType((ParameterizedType) type.getType()));
TypeAdapter<List<Object>> delegate = (TypeAdapter<List<Object>>)
gson.getDelegateAdapter(this, collectionElementType);
return (TypeAdapter<T>) new SingletonListTypeAdapter<>(delegate);
}
};
private final TypeAdapter<T> delegate;
SingletonListTypeAdapter(TypeAdapter<T> delegate) {
this.delegate = delegate;
}
#Override public void write(JsonWriter out, List<T> value) throws IOException {
out.beginArray();
for (int i = 0, size = value.size(); i < size; i++) {
delegate.write(out, value.get(i));
}
out.endArray();
}
#Override public List<T> read(JsonReader in) throws IOException {
if (in.peek() != BEGIN_ARRAY) {
return Collections.singletonList(delegate.read(in));
}
in.beginArray();
List<T> expanding = new ArrayList<>();
while (in.hasNext()) {
expanding.add(delegate.read(in));
}
in.endArray();
return Collections.unmodifiableList(expanding);
}
static Type getCollectionElementType(ParameterizedType type) {
Type[] types = type.getActualTypeArguments();
Type paramType = types[0];
if (paramType instanceof WildcardType) {
return ((WildcardType) paramType).getUpperBounds()[0];
}
return paramType;
}
}
Related
I have a class that has an Object[] field to store values and I need to be able to deserialize it with gson such that specialized floating point values are Double and not String.
Here is some test code. The second assert fails, because the special values are deserialized as Strings:
#Test
public void testGson() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
ValuesObject toJson = new ValuesObject(0, new Object[] { 0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN });
String json = gson.toJson(toJson);
assertEquals("{\"type\":0,\"values\":[0.0,Infinity,-Infinity,NaN]}", json);
ValuesObject fromJson = gson.fromJson(json, ValuesObject.class);
assertEquals(toJson, fromJson); // fails
}
private class ValuesObject {
private int type;
private Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
// snip... equals and hashCode
}
I know that the answer lies in a custom TypeAdapter or Deserializer, but I don't see the best approach. All I really want to do is override ObjectTypeAdapter to handle these three special case String values, but the code is not extendable. It seems that an entire adapter needs to be implemented.
Based on https://stackoverflow.com/a/64949367/844123, I have something that works, but is it the best way to do it?
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
private ValuesObjectTypeAdapterFactory() { }
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
#SuppressWarnings("unchecked")
TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
// Cast is safe because `T` is ValuesObject or subclass (due to `type` check at method start)
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
#Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
#Override
public Object read(JsonReader in) {
JsonElement jsonObject = new JsonParser().parse(in);
Object value = delegate.fromJsonTree(jsonObject);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
In case only that specific field should be deserialized in this way, you can use #JsonAdapter on that field instead of registering the type adapter factory globally with a GsonBuilder to avoid affecting other unrelated fields. You then need to replace the call to gson.getDelegateAdapter with gson.getAdapter due to this Gson issue.
You should avoid using JsonParser here; it always parses the JSON in lenient mode regardless of the JsonReader setting (though it does not matter much here because you have to use lenient mode anyways to parse the non-finite numbers). Unfortunately that is currently not well documented. Instead you can use gson.getAdapter(JsonElement.class) and use the returned adapter to parse the JSON. However, for your use case it is not even needed to parse the JSON as JsonElement, you can instead directly call delegate.read(in).
The following shows the recommended changes. It uses "diff-like" formatting where every line starting with - should be removed, and every line starting with + should be added:
+/**
+ * Should only be used with Gson's {#link JsonAdapter}; otherwise infinite recursion can occur.
+ */
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
- private ValuesObjectTypeAdapterFactory() { }
+ // Default constructor for Gson's #JsonAdapter
+ public ValuesObjectTypeAdapterFactory() { }
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
#SuppressWarnings("unchecked")
- TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
+ // Uses `getAdapter` as workaround for https://github.com/google/gson/issues/1028
+ TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getAdapter(type);
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
#Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
#Override
public Object read(JsonReader in) throws IOException {
- JsonElement jsonObject = new JsonParser().parse(in);
-
- Object value = delegate.fromJsonTree(jsonObject);
+ Object value = delegate.read(in);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
private class ValuesObject {
private final int type;
+ #JsonAdapter(ValuesObjectTypeAdapterFactory.class)
private final Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(values);
result = prime * result + Objects.hash(type);
return result;
}
// snip... equals and hashCode
}
I have bunch of model classes which have fields of type List<X> where X is one of many things (e.g. String, Integer, but also some of my own types). I'm using GSON to parse JSON representations of these models.
My problem is that the server I'm dealing with (which is beyond my control) somehow removed singleton arrays and replaces them by the contained object.
For example, instead of returning:
{
"foo": [ "bar"],
"bleh": [ { "some": "object" } ]
}
It returns:
{
"foo": "bar",
"bleh": { "some": "object" }
}
Now assume that the Java model class look something like this:
public class Model {
private List<String> foo;
private List<SomeObject> bleh;
}
Currently this causes GSON to throw an exception because it finds BEGIN_STRING or BEGIN_OBJECT where it expects BEGIN_ARRAY.
For arrays or lists of Strings this is easily solved using a TypeAdapter<List<String>>. But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter<List<?>>, because at some point you need to know the type.
So is there another way to configure GSON to be smart enough to turn single objects or values into arrays/lists? Or in other words, just "pretend" that the [ and ] are there where it expects to find them although they aren't there?
But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter>, because at some point you need to know the type.
This is what type adapter factories are designed for: you can control every type in Gson instance configuration.
final class AlwaysListTypeAdapterFactory<E>
implements TypeAdapterFactory {
// Gson can instantiate it itself
private AlwaysListTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a List -- just delegate the job to Gson and let it pick the best type adapter itself
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Resolving the list parameter type
final Type elementType = resolveTypeArgument(typeToken.getType());
#SuppressWarnings("unchecked")
final TypeAdapter<E> elementTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(elementType));
// Note that the always-list type adapter is made null-safe, so we don't have to check nulls ourselves
#SuppressWarnings("unchecked")
final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(elementTypeAdapter).nullSafe();
return alwaysListTypeAdapter;
}
private static Type resolveTypeArgument(final Type type) {
// The given type is not parameterized?
if ( !(type instanceof ParameterizedType) ) {
// No, raw
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public void write(final JsonWriter out, final List<E> list) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
// This is where we detect the list "type"
final List<E> list = new ArrayList<>();
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
// If it's a regular list, just consume [, <all elements>, and ]
in.beginArray();
while ( in.hasNext() ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
break;
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
// An object or a primitive? Just add the current value to the result list
list.add(elementTypeAdapter.read(in));
break;
case NULL:
throw new AssertionError("Must never happen: check if the type adapter configured with .nullSafe()");
case NAME:
case END_ARRAY:
case END_OBJECT:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError("Must never happen: " + token);
}
return list;
}
}
}
Now you just have to tell Gson which fields are not well-formed.
Of course, you might configure the whole Gson instance to accept such lists, but let it be more precise using the #JsonAdapter annotation:
final class Model {
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<String> foo = null;
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<SomeObject> bleh = null;
#Override
public String toString() {
return "Model{" + "foo=" + foo + ", bleh=" + bleh + '}';
}
}
final class SomeObject {
final String some = null;
#Override
public String toString() {
return "SomeObject{" + "some='" + some + '\'' + '}';
}
}
Test data:
single.json
{
"foo": "bar",
"bleh": {"some": "object"}
}
list.json
{
"foo": ["bar"],
"bleh": [{"some": "object"}]
}
Example:
private static final Gson gson = new Gson();
public static void main(final String... args)
throws IOException {
for ( final String resource : ImmutableList.of("single.json", "list.json") ) {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43412261.class, resource) ) {
final Model model = gson.fromJson(jsonReader, Model.class);
System.out.println(model);
}
}
}
And the output:
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
You can simply write your own JsonDeserializer where you check whether your bleh or foo are JsonObjects or JsonArrays.
To check if a JsonElement is an array or an object:
JsonElement element = ...;
if (element.isJsonObject()) {
//element is a JsonObject
} else if (element.isJsonArray()) {
//element is a JsonArray
}
One solution to this would be to write a custom TypeAdapterFactory which creates an adapter which peeks at the JSON data. If it encounters something other than a JSON array (or JSON null) it wraps it inside a JSON array before deserializing it:
// Only intended for usage with #JsonAdapter on fields
class SingleValueOrListAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Note: Cannot use getDelegateAdapter due to https://github.com/google/gson/issues/1028
TypeAdapter<T> listAdapterDelegate = gson.getAdapter(type);
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
listAdapterDelegate.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
JsonToken peeked = in.peek();
if (peeked == JsonToken.NULL || peeked == JsonToken.BEGIN_ARRAY) {
return listAdapterDelegate.read(in);
} else {
// Wrap JSON element in a new JSON array before deserializing it
JsonElement jsonElement = jsonElementAdapter.read(in);
JsonArray jsonArray = new JsonArray();
jsonArray.add(jsonElement);
return listAdapterDelegate.fromJsonTree(jsonArray);
}
}
};
}
}
The above implementation is designed only for usage with #JsonAdapter on fields, for example:
#JsonAdapter(SingleValueOrListAdapterFactory.class)
private List<MyClass> myField;
Compared to the currently accepted answer this provides the following advantages because it simply delegates the actual deserialization to listAdapterDelegate:
Custom List (or Collection) subclasses are supported because creation of them is delegated to Gson
Gson's default type resolution logic is used to determine the element type and to deserialize it
But it also has the following disadvantage:
Decreased performance because if the data is not already in a JSON array it is first deserialized to a JsonElement before the actual deserialization is performed
When using the GSON library, you could just check whether or not the following token is an object or an array. This of course requires you to go more fine grained while parsing the XML, but it allows you full control of what do you want to get from it. Sometimes we are not under control of the XML, and it could come handy.
This is an example to check if the next token is an object or an array, using the JsonReader class to parse the file:
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
jsonReader.beginArray()
} else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
jsonReader.beginObject()
}
And at the end of the array / object, you could do the same, but for the end tokens:
if (jsonReader.peek() == JsonToken.END_ARRAY) {
jsonReader.endArray()
} else if (jsonReader.peek() == JsonToken.END_OBJECT) {
jsonReader.endObject()
}
This way, you could have identical code (adding an extra check, to verify if you are on an array or on an object) to parse your array of objects, or a single object.
I had this same problem consuming xml / json from a vendor - they certainly weren't going to change their code for me :) There were several resources on the web that I used before changing adapting them to my own version This SO answer was very helpful. I spent some time looking at the gson code and finding a lot of private variable that I wanted access to. So, essentially what my custom collection adapter does is peek to see if the next element is an object. If not, we just delegate the read to the previous adapter (that we have overridden).
If the next element is an object, we use gson to process that. We then convert that to an array of one object. Use gson to write that to a string, then pass that string as a JsonReader to the underlying adapter. This can then create an instance of the underlying list and add the one element we have.
Here's the AdapterTypeFactory:
public enum ListSingleObjectAdapterFactory implements TypeAdapterFactory {
INSTANCE; // Josh Bloch's Enum singleton pattern
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
TypeAdapter collectionAdapter = gson.getDelegateAdapter(this, typeToken);
Class genericClass = (Class) ((ParameterizedType) typeToken.getType())
.getActualTypeArguments()[0];
return new SingleObjectOrCollectionAdapter(
gson, collectionAdapter, genericClass);
}
}
Then the type adapter I have is:
public class SingleObjectOrCollectionAdapter<T> extends TypeAdapter<Collection<T>> {
private Class<T> adapterclass;
private Gson gson;
private TypeAdapter arrayTypeAdapter;
public SingleObjectOrCollectionAdapter(Gson gson, TypeAdapter<T> collectionTypeAdapter, Class<T> componentType) {
arrayTypeAdapter = collectionTypeAdapter;
this.gson = gson;
adapterclass = componentType;
}
#Override
public Collection<T> read(JsonReader reader) throws IOException {
Collection<T> collection;
JsonReader myReader = reader;
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
T inning = gson.fromJson(reader, adapterclass);
String s = gson.toJson(new Object[]{inning});
myReader = new JsonReader(new StringReader(s));
}
collection = (Collection)arrayTypeAdapter.read( myReader );
return collection;
}
#Override
public void write(JsonWriter writer, Collection<T> value) throws IOException {
arrayTypeAdapter.write(writer, value);
}
}
Finally, we need to register the adapter factory:
GsonBuilder gb = new GsonBuilder().registerTypeAdapterFactory(ListSingleObjectAdapterFactory.INSTANCE);
So far, it seems to be working well handling both single and multiple objects - although I wouldn't be surprised if it needs some tweaking down the road.
I have an API that can return JSON arrays or objects. Example JSON object
{
"id": 1,
"name": "name"
}
JSON array:
[
{
"id": 1,
"name": "name"
},
{
"id": 1,
"name": "name"
}
]
When mapping a JSON object response to a POJO I use:
MyEntity myEntity = new Gson().fromJson(jsonString, MyEntity.class);
When mapping a JSON array response to an array of POJOs I use:
MyEntity[] myEntity = new GSON().fromJson(jsonString, MyEntity[].class);
How can I convert those two responses to the appropriate types dynamically?
NOTE: I can't modify the server response, this is a public API.
Thank you!
EDIT:
I am trying to implement a method that does this automatically but I am missing something. The method
public <T> T convertResponseToEntity(Class<T> classOfT)
{
JsonElement jsonElement = this.gson.fromJson(getResponseAsString(), JsonElement.class);
if (jsonElement.isJsonArray()) {
Type listType = new TypeToken<T>(){}.getType();
return this.gson.fromJson(getResponseAsString(), listType);
}
return this.gson.fromJson(getResponseAsString(), (Type) classOfT);
}
It returns a list of LinkedTreeMaps. How can I modify the code to return the same content as Object[]?
How can I convert those 2 responses dynamically to the appropriate type?
It depends on how to interpret the "appropriate type" here because it would lead to instanceof or visitor pattern to get the appropriate type once you try to handle the parsed-from-JSON object every time you need it. If you can't change the API, you can smooth the way you use it. One of possible options here is handling such response as if everything is a list. Even a single object can be handled as a list with one element only (and many libraries work with sequences/lists only having that fact: Stream API in Java, LINQ in .NET, jQuery in JavaScript, etc).
Suppose you have the following MyEntity class to handle the elements obtained from the API you need:
// For the testing purposes, package-visible final fields are perfect
// Gson can deal with final fields too
final class MyEntity {
final int id = Integer.valueOf(0); // not letting javac to inline 0 since it's primitive
final String name = null;
#Override
public String toString() {
return id + "=>" + name;
}
}
Next, let's create a type adapter that will always align "true" lists and single objects as if it were a list:
final class AlwaysListTypeAdapter<T>
extends TypeAdapter<List<T>> {
private final TypeAdapter<T> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
static <T> TypeAdapter<List<T>> getAlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
return new AlwaysListTypeAdapter<>(elementTypeAdapter);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final List<T> list)
throws IOException {
if ( list == null ) {
out.nullValue();
} else {
switch ( list.size() ) {
case 0:
out.beginArray();
out.endArray();
break;
case 1:
elementTypeAdapter.write(out, list.iterator().next());
break;
default:
out.beginArray();
for ( final T element : list ) {
elementTypeAdapter.write(out, element);
}
out.endArray();
break;
}
}
}
#Override
public List<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
final List<T> list = new ArrayList<>();
in.beginArray();
while ( in.peek() != END_ARRAY ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return unmodifiableList(list);
case BEGIN_OBJECT:
return singletonList(elementTypeAdapter.read(in));
case NULL:
return null;
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
// A guard case: what if Gson would add another token someday?
throw new AssertionError("Must never happen: " + token);
}
}
}
Gson TypeAdapter are designed to work in streaming fashion thus they are cheap from the efficiency perspective, but not that easy in implementation. The write() method above is implemented just for the sake of not putting throw new UnsupportedOperationException(); there (I'm assuming you only read that API, but don't know if that API might consume "either element or a list" modification requests). Now it's necessary to create a type adapter factory to let Gson pick up the right type adapter for every particular type:
final class AlwaysListTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory();
private AlwaysListTypeAdapterFactory() {
}
static TypeAdapterFactory getAlwaysListTypeAdapterFactory() {
return alwaysListTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken)
throws IllegalArgumentException {
if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
final Type elementType = getElementType(typeToken);
// Class<T> instances can be compared with ==
final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class ? gson.getAdapter(MyEntity.class) : null;
// Found supported element type adapter?
if ( elementTypeAdapter != null ) {
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getAlwaysListTypeAdapter(elementTypeAdapter);
return castTypeAdapter;
}
}
// Not a type that can be handled? Let Gson pick a more appropriate one itself
return null;
}
// Attempt to detect the list element type
private static Type getElementType(final TypeToken<?> typeToken) {
final Type listType = typeToken.getType();
return listType instanceof ParameterizedType
? ((ParameterizedType) listType).getActualTypeArguments()[0]
: Object.class;
}
}
And how it's used after all:
private static final Type responseItemListType = new TypeToken<List<MyEntity>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getAlwaysListTypeAdapterFactory())
.create();
public static void main(final String... args) {
test("");
test("{\"id\":1,\"name\":\"name\"}");
test("[{\"id\":1,\"name\":\"name\"},{\"id\":1,\"name\":\"name\"}]");
test("[]");
}
private static void test(final String incomingJson) {
final List<MyEntity> list = gson.fromJson(incomingJson, responseItemListType);
System.out.print("LIST=");
System.out.println(list);
System.out.print("JSON=");
gson.toJson(list, responseItemListType, System.out); // no need to create an intermediate string, let it just stream
System.out.println();
System.out.println("-----------------------------------");
}
The output:
LIST=null
JSON=null
-----------------------------------
LIST=[1=>name]
JSON={"id":1,"name":"name"}
-----------------------------------
LIST=[1=>name, 1=>name]
JSON=[{"id":1,"name":"name"},{"id":1,"name":"name"}]
-----------------------------------
LIST=[]
JSON=[]
-----------------------------------
Just parse it into JsonElement and check actual element type:
Gson g = new Gson();
JsonParser parser = new JsonParser();
JsonElement e = parser.parse( new StringReader(jsonString) );
if(e instanceof JsonObject) {
MyEntity myEntity = g.fromJson(e, MyEntity.class);
} else {
MyEntity[] myEntity = g.fromJson(e, MyEntity[].class);
}
I have some odd JSON like:
[
{
"type":"0",
"value":"my string"
},
{
"type":"1",
"value":42
},
{
"type":"2",
"value": {
}
}
]
Based on some field, the object in the array is a certain type.
Using Gson, my thought is to have a TypeAdapterFactory that sends delegate adapters for those certain types to a TypeAdapter, but I'm hung up on understanding a good way of reading that "type" field to know which type to create.
In the TypeAdapter,
Object read(JsonReader in) throws IOException {
String type = in.nextString();
switch (type) {
// delegate to creating certain types.
}
}
would assume the "type" field comes first in my JSON. Is there a decent way to remove that assumption?
Here is some code I wrote to handle an array of NewsFeedArticle and NewsFeedAd items in Json. Both items implement a marker interface NewsFeedItem to allow me to easily check if the TypeAdater should be used for a particular field.
public class NewsFeedItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!NewsFeedItem.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedArticle.class));
TypeAdapter<NewsFeedAd> newsFeedAdAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedAd.class));
return (TypeAdapter<T>) new NewsFeedItemTypeAdapter(jsonElementAdapter, newsFeedArticleAdapter, newsFeedAdAdapter).nullSafe();
}
private static class NewsFeedItemTypeAdapter extends TypeAdapter<NewsFeedItem> {
private final TypeAdapter<JsonElement> jsonElementAdapter;
private final TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter;
private final TypeAdapter<NewsFeedAd> newsFeedAdAdapter;
NewsFeedItemTypeAdapter(TypeAdapter<JsonElement> jsonElementAdapter,
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter,
TypeAdapter<NewsFeedAd> newsFeedAdAdapter) {
this.jsonElementAdapter = jsonElementAdapter;
this.newsFeedArticleAdapter = newsFeedArticleAdapter;
this.newsFeedAdAdapter = newsFeedAdAdapter;
}
#Override
public void write(JsonWriter out, NewsFeedItem value) throws IOException {
if (value.getClass().isAssignableFrom(NewsFeedArticle.class)) {
newsFeedArticleAdapter.write(out, (NewsFeedArticle) value);
} else if (value.getClass().isAssignableFrom(NewsFeedAd.class)) {
newsFeedAdAdapter.write(out, (NewsFeedAd) value);
}
}
#Override
public NewsFeedItem read(JsonReader in) throws IOException {
JsonObject objectJson = jsonElementAdapter.read(in).getAsJsonObject();
if (objectJson.has("Title")) {
return newsFeedArticleAdapter.fromJsonTree(objectJson);
} else if (objectJson.has("CampaignName")) {
return newsFeedAdAdapter.fromJsonTree(objectJson);
}
return null;
}
}
}
You can then register this with Gson using the following code.
return new GsonBuilder()
.registerTypeAdapterFactory(new NewsFeedItemTypeAdapterFactory())
.create();
I have an enum:
enum Type {
LIVE, UPCOMING, REPLAY
}
And some JSON:
{
"type": "live"
}
And a class:
class Event {
Type type;
}
When I try to deserialize the JSON, using GSON, I receive null for the Event type field, since the case of the type field in the JSON does not match that of the enum.
Events events = new Gson().fromJson(json, Event.class);
If I change the enum to the following, then all works fine:
enum Type {
live, upcoming, replay
}
However, I would like to leave the enum constants as all uppercase.
I'm assuming I need to write an adapter but haven't found any good documentation or examples.
What is the best solution?
Edit:
I was able to get a JsonDeserializer working. Is there a more generic way to write this though, as it would be unfortunate to have to write this each time there is a case mismatch between enum values and JSON strings.
protected static class TypeCaseInsensitiveEnumAdapter implements JsonDeserializer<Type> {
#Override
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
return Type.valueOf(json.getAsString().toUpperCase());
}
}
A simpler way I found (just now) to do this is to use the #SerializedName annotation. I found it in the EnumTest.java here (the Gender class around ln 195):
https://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/EnumTest.java?r=1230
This assumes that all of your Types will come in as lowercase as opposed to being "case insensitive"
public enum Type {
#SerializedName("live")
LIVE,
#SerializedName("upcoming")
UPCOMING,
#SerializedName("replay")
REPLAY;
}
This was the simplest and most generic way I found to do this. Hope it helps you.
Now you can add multiple values for #SerializedName like this:
public enum Type {
#SerializedName(value = "live", alternate = {"LIVE"})
LIVE,
#SerializedName(value = "upcoming", alternate = {"UPCOMING"})
UPCOMING,
#SerializedName(value = "replay", alternate = {"REPLAY"})
REPLAY;
}
I think it's a bit late for you but I hope it will help anyone else!
Conveniently for you, this is very close to the example given in TypeAdapterFactory's Javadoc:
public class CaseInsensitiveEnumTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (!rawType.isEnum()) {
return null;
}
final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
for (T constant : rawType.getEnumConstants()) {
lowercaseToConstant.put(toLowercase(constant), constant);
}
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(toLowercase(value));
}
}
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
} else {
return lowercaseToConstant.get(toLowercase(reader.nextString()));
}
}
};
}
private String toLowercase(Object o) {
return o.toString().toLowerCase(Locale.US);
}
}
This is a rather old question, but the accepted answer didn't work for me, and using #SerializedName is not enough because I want to make sure I can match "value", "Value" and "VALUE".
I managed to make a generic Adapter based on the code posted in the question:
public class UppercaseEnumAdapter implements JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context)
throws JsonParseException {
try {
if(type instanceof Class && ((Class<?>) type).isEnum())
return Enum.valueOf((Class<Enum>) type, json.getAsString().toUpperCase());
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
And to use it:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyEnum.class, new UppercaseEnumAdapter());
Gson gson = gsonBuilder.create();