Custom adapter based on a runtime type of a Collection? (hibernate PersistentBags/PersistentSets)

1,347 views
Skip to first unread message

Alex Z

unread,
Aug 21, 2013, 3:59:51 AM8/21/13
to googl...@googlegroups.com
I want to use Gson to use a custom adapter for Java Entities that contains proxies: PersistentBags and PersistentSets. 

The problem is that if I do something like:

new GsonBuilder().registerTypeHierarchyAdapter(PersistentBag.class, new PersistentBagAdapter());

The adapter is never used because gson looks for the declared type wich is List and not for the runtime type. CollectionTypeAdapterFactory is used also for PersistentBags.

I have to extend CollectionTypeAdapterFactory and register as factory. But CollectionTypeAdapterFactory  is a final class.

If I copy the content of CollectionTypeAdapterFactory in my HibernateCollectionTypeAdapterFactory, I cannot obtain the current Gson.constructorConstructor instance because is private, which is needed by the CollectionTypeAdapterFactory to create list elements. Another problem is that TypeAdapterRuntimeTypeWrapper has a package level constructor.


Is there a simple way to associate a custom adapter based on a runtime type of a Collection?





Alex Z

unread,
Aug 22, 2013, 12:18:42 PM8/22/13
to googl...@googlegroups.com
Attached testcase demonstrates that custom TestAdapter is only used if collection isn't parametrized.

the output of the test case:

{"collection":["TypeAdapted-test"],"parametrizedCollection":["test"]}

I expect that should be: 

{"collection":["TypeAdapted-test"],"parametrizedCollection":["TypeAdapted-test"]}

The problem is in the class: com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper

  /**
   * Finds a compatible runtime type if it is more specific
   */
  private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
    if (value != null
        && (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
      type = value.getClass();
    }
    return type;
  }

When "collection" is parsed type is Class(java.util.Collection) and type = value.getClass(); is executed, so registered type adapter for the runtime type is used

When "parametrizedCollection" is parsed type is com.google.gson.internal.$Gson$Types$ParametrizedTypeImpl (raw type java.util.Collection<java.lang.String> ) and type = value.getClass(); isn't executed,


Test Case:

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

public class TestGson {
public static void main(String[] args) {

TestEntity t = new TestEntity();
List<String> lst = new ArrayList<String>();
lst.add("test");

t.collection = lst;
t.parametrizedCollection = lst;
Gson gson = new GsonBuilder().registerTypeAdapter(ArrayList.class, new TestAdapter()).create();

String json = gson.toJson(t);
System.out.println(json);
}
}

class TestEntity {
public Collection collection;
public Collection<String> parametrizedCollection;
}

class TestAdapter extends TypeAdapter<List> {

@Override
public List read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
        }

//...

return new ArrayList();
}

@Override
public void write(JsonWriter out, List value) throws IOException {
if (value == null) {
out.nullValue();
       return;
   }

out.beginArray();

for (Object element : value) {
out.value("TypeAdapted-" + element);
}
out.endArray();
return;

Brandon Mintern

unread,
Aug 22, 2013, 1:06:50 PM8/22/13
to google-gson
You should not register the concrete type that you *want* to produce (e.g., PersistentBag, ArrayList), but the abstract type that you want to be responsible for. That is, your test should register a TypeAdapter for Collection instead of ArrayList. Of course, in the implementation, you can return an ArrayList because it is a collection.

Likewise, for your actual example, you'll want to register a TypeAdapter for those types for which you would like to use a PersistentBag. So register type adapters for, e.g., List.class and Set.class, or Collection.class if that is what you declare your fields' types to be.


--
You received this message because you are subscribed to the Google Groups "google-gson" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-gson...@googlegroups.com.
To post to this group, send email to googl...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-gson.
For more options, visit https://groups.google.com/groups/opt_out.

Alex Z

unread,
Aug 23, 2013, 10:45:15 AM8/23/13
to googl...@googlegroups.com
If I register in my TestCase the TypeAdapter in this way (and change it's implementation to be a TypeAdapter<Collection>):

Gson gson = new GsonBuilder().registerTypeAdapter(Collection.class, new TestAdapter()).create();

I obtain:

{"collection":["test"],"parametrizedCollection":["test"]}

The TestAdapter is never used. 

This happens because for "collection" into TypeAdapterRuntimeTypeWrapper the chosen TypeAdapter is TestAdapter, but getRuntimeTypeIfMoreSpecific selects type ArrayList and then the runtimeTypeAdapter changes to com.google.gson.internal.bind.CollectionTypeAdapterFactory

Instead for "parametrizedCollection"  the chosen TypeAdapter is com.google.gson.internal.bind.CollectionTypeAdapterFactory and getRuntimeTypeIfMoreSpecific doesen't change the chosen adapter.

Tim Whitbeck

unread,
Mar 5, 2014, 11:36:00 AM3/5/14
to googl...@googlegroups.com
I'm jumping in on this, because this thread is the first result for a google search of "gson hibernate persistentcollection".

Hibernate proxy handling has already thoroughly been addressed at SO: http://stackoverflow.com/questions/13459718/could-not-serialize-object-cause-of-hibernateproxy

But this doesn't help with the issue of serializing PersistentCollections. What I wanted in my application is to serialize collections only if they're already initialized.

First, my code:

public class PersistentCollectionCheckingTypeAdapter extends TypeAdapter<Collection> {
  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      if (Collection.class.isAssignableFrom(type.getRawType())) {
        TypeAdapter delegate = gson.getDelegateAdapter(this, type);

        return (TypeAdapter<T>)new PersistentCollectionCheckingTypeAdapter(delegate);
      }

      return null;
    }
  };

  private final TypeAdapter delegate;

  PersistentCollectionCheckingTypeAdapter(TypeAdapter delegate) {
    this.delegate = delegate;
  }

  @Override
  public void write(JsonWriter out, Collection value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }

    // This catches non-proxied collections AND initialized proxied collections
    if (Hibernate.isInitialized(value)) {
      delegate.write(out, value);
      return;
    }

    // write out null for uninitialized proxied collections
    out.nullValue();
  }

  @Override
  public Collection read(JsonReader in) throws IOException {
    return (Collection)delegate.read(in);
  }
}

Then I register the factory with my Gson instance:

gsonBuilder.registerTypeAdapterFactory(PersistentCollectionCheckingTypeAdapter.FACTORY);

Explanation:

This TypeAdapter simply uses Hibernate.isInitialized to check if a collection is either:
1) An initialized hibernate PersistentCollection OR
2) A normal collection (not a hibernate PersistentCollection)
 
If so, the delegate adapter is used to serialize, otherwise null is written out. This relies on the fact that Hibernate.isInitialized returns false if and only if the Collection is a PersistentCollection that is not initialized yet. I believe that's the case for all versions of Hibernate.

Hopefully this is of use to someone.

Ioram Gordadze

unread,
Jul 5, 2018, 5:24:28 PM7/5/18
to google-gson
Thanks Timothy Whitbeck, 

I was trying to prevent lazy collection initialization during object serialization and your TypeAdapter implementation helped me a lot.
Reply all
Reply to author
Forward
0 new messages