Data duplication during serialization

13 views
Skip to first unread message

Maverick

unread,
Sep 15, 2011, 6:23:12 AM9/15/11
to Google Web Toolkit

I'm having a strange behavior (at least, something that I don't
understand) during derialization/deserialization, which causes values
to be duplicated to a List.

I have a custom Map that basically adds key order preserving through
an ArrayList. Each time a key/value pair is put into the map, the key
is also added to an ArrayList. When a class instance is passed through
a RPC the map is serialized correctly (or at least the data is the
same before and after); but the list seems to be deserialized twice
and two copies of the keys exist after.

In the code below, I instantiate the CustomMap and add the key "10"; I
expect to have one key received on the server and one key sent back to
the client. However, the output is the following:
Before sending: [10]
On server: [10, 10]
Received back: [10, 10, 10]

Am I missing some GWT programming principle?
How could I implement such feature without suffering that problem?

Thanks

Here the code:

/* The custom map; to every put, the key is also added to the list.
*/
public class CustomMap<K, V> extends HashMap<K, V> implements
Serializable, IsSerializable
{
private static final long serialVersionUID = -1780691944208423396L;

ArrayList<K> list = new ArrayList<K>();

public CustomMap()
{

}

@Override
public V put( K key, V value )
{
return put( key, value, this.list.size() );
}

public V put( K key, V value, int index )
{
this.list.add( index, key );
return super.put( key, value );
}

public V get( int n )
{
return get( key( n ) );
}

public int indexOf( K key )
{
return this.list.indexOf( key );
}

public K key( int n )
{
return this.list.get( n );
}

public ArrayList<K> getData()
{
return this.list;
}
}


/* The RPC call; just send a CustomMap forth and back
*/
public void onModuleLoad()
{
CustomMap<Long, String> map = new CustomMap<Long, String>();

map.put( new Long( 10 ), "a value" );

System.out.println( "Before sending: " + map.getData() );

service.transfer( map, new AsyncCallback<CustomMap<Long, String>>(){

@Override
public void onSuccess( CustomMap<Long, String> result )
{
System.out.println( "Received back: " + result.getData() );
}

@Override
public void onFailure( Throwable caught ) {}
} );
}

/* Server-side code, for completeness
*/

public CustomMap<Long, String> transfer( CustomMap<Long, String>
map )
{
System.out.println( "On server: " + map.getData() );

return map;
}

Thomas Broyer

unread,
Sep 15, 2011, 7:04:12 AM9/15/11
to google-we...@googlegroups.com
I think GWT-RPC uses the CustomSerializer for the HashMap (which calls put() for each key/value pair) in addition to deserializing the ArrayList. The custom-serializer will then call your put() methods which will add to the list.
Marking the ArrayList 'transient' or @GwtTransient should be enough to fix it (the list won't be serialized, but instead rebuilt by the put() calls when the object is deserialized). You could also wrap a HashMap instead of extending hashMap.

BTW, your code is lively buggy, as you unconditionnally add to the list, without ever checking whether it already contains an entry for the key (i.e. put("a", "b"); put("a", "c"); will give you ["a", "a"] in getData(), whereas the Map only contains a single entry, for key "a")

Paul Robinson

unread,
Sep 15, 2011, 7:05:25 AM9/15/11
to google-we...@googlegroups.com
Your implementation doesn't handle the case where adding a value to the map overwrites an existing entry. In that case, you shouldn't add a new entry to your list of keys, but replace one of them.

When the map is transferred over RPC, the key list is sent with the data. When RPC reconstitutes the map, it adds key,value pairs one at a time. Unforunately, since you start with a non-empty list of keys, as it rebuilds the map and calls put(K,V), it will add a new key each time.

Even if your implementation overcame the above problem, RPC will still send the keys twice over the wire - once embedded in the map, and once in your list of keys. So if you really want an implementation like this, you ought to write your own custom field serializer to make the on-the-wire format more efficient. You would also be able to control the way the map was reconstituted from its parts.

OTOH, If you can do without your get(int) method, you could just use a LinkedHashMap instead. It remembers insertion order.


Paul

Maverick

unread,
Sep 15, 2011, 8:52:25 AM9/15/11
to Google Web Toolkit
Thank to both of you for your quick reply.

The CustomMap implementation posted here is just a small test case
that reproduces the problem, so I'm aware there are a lot of things
missing. Although, for the use we are using, duplicating a key due to
multiple put() is acceptable.

The solution of making the list transient easily solves the problem
without having to change the implementation.

However, I still don't understand why using put() for deserialization
was necessary; isn't it a slowdown to deserialize fields twice? What
problems can emerge otherwise? (I ask this to improve my knowledge on
GWT)

Thanks

Thomas Broyer

unread,
Sep 15, 2011, 9:05:11 AM9/15/11
to google-we...@googlegroups.com
Have a look at http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/gwt/user/client/rpc/core/java/util/HashMap_CustomFieldSerializer.java (and http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/gwt/user/client/rpc/core/java/util/Map_CustomFieldSerializerBase.java, where all the logic is) for how maps are serialized in GWT-RPC.

GWT won't "deserialize fields twice", it's just that, to deserialize a HashMap, it creates an empty one and then populates it. This doesn't play well with the fact that your customized HashMap fills a field from the put() methods, field that is de/serialized independently from the map itself.
Reply all
Reply to author
Forward
0 new messages