Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ObjectMapper.readValue fails for TreeSet for byte[] (and other types that are not Comparable) #255

Closed
webbean opened this issue Jul 2, 2013 · 9 comments

Comments

@webbean
Copy link

webbean commented Jul 2, 2013

We use jackson-databind-2.2.2 to serialize and deserialize NavigableSet<byte[]>.
Serializing works fine, but deserializing throws java.lang.ClassCastException:

Exception in thread "main" java.lang.ClassCastException: [B cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(TreeMap.java:1188)
    at java.util.TreeMap.put(TreeMap.java:531)
    at java.util.TreeSet.add(TreeSet.java:255)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:234)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2120)

Below is the code:

public static void main(String[] args) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    NavigableSet<byte[]> set = new TreeSet<>(Bytes.BYTES_COMPARATOR);
    set.add(Bytes.toBytes("ROW1"));
    set.add(Bytes.toBytes("ROW2"));
    set.add(Bytes.toBytes("ROW3"));
    byte[] array = mapper.writeValueAsBytes(set);
    NavigableSet<byte[]> result = mapper.readValue(array,
            new TypeReference<NavigableSet<byte[]>>() {
            });
    for (byte[] value : result) {
        System.out.println(Bytes.toString(value));
    }
}

It seems that we need provide a Comparator parameter(such as Bytes.BYTES_COMPARATOR) when we create a new TreeSet object, but how can we do that in TypeReference when deserializing by mapper.readValue method.

@cowtowncoder
Copy link
Member

Interesting.

One work-around that might work (but one I have not tried) would be to use 'updating reader':

NavigableSet<byte[]> result = ...;
mapper.readerForUpdating(result).read(array);

in which case existing map will be used.

I will see if I can find a fix for the underlying issue.

@webbean
Copy link
Author

webbean commented Jul 3, 2013

Thank you very much.
We solved this problem by first deserializing it to a Set, and then converting it to NavigableSet, like below:

public static void main(String[] args) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    NavigableSet<byte[]> set = new TreeSet<>(Bytes.BYTES_COMPARATOR);
    set.add(Bytes.toBytes("ROW1"));
    set.add(Bytes.toBytes("ROW2"));
    set.add(Bytes.toBytes("ROW3"));
    byte[] array = mapper.writeValueAsBytes(set);
    Set<byte[]> result = mapper.readValue(array,
            new TypeReference<Set<byte[]>>() {
            });

    NavigableSet<byte[]> temp = new TreeSet<>(Bytes.BYTES_COMPARATOR);
    temp.addAll(result);

    for (byte[] value : temp) {
        System.out.println(Bytes.toString(value));
    }
}

But the following code would be the best we think (if it can work).

    NavigableSet<byte[]> result = mapper.readValue(array,
            new TypeReference<TreeSet<byte[]>>() {
            });

@cowtowncoder
Copy link
Member

Oh. There is actually something that should help you solve your problem. Just create a sub-class like:

 MyByteArraySet extends TreeSet<byte[]> {
     public MyByteArraySet() { super(Bytes.BYTES_COMPARATOR); }
 }

and then either use this type instead of TreeSet<byte[]>. This would allow proper deserialization.
It's not a particularly elegant solution, but would make things work.

@webbean
Copy link
Author

webbean commented Jul 5, 2013

Thanks, we have changed the solution to the one you provided, it's more effective than ours.

@cowtowncoder
Copy link
Member

Ok good that it works. I hope that eventually there could be better solutions; I will edit title of this entry a bit, but leave it open.

@webbean
Copy link
Author

webbean commented Jul 7, 2013

hi, cowtowncoder
Unfortunately, we put the MyByteArraySet sub-class into our test envionment, but the following exception thrown.

Exception in thread "main" java.lang.IllegalStateException: No default constructor for [collection type; class Test$MyByteArraySet, contains [array type, component type: [simple type, class byte]]]
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:221)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2120

The demo source is below:

import java.util.NavigableSet;
import java.util.TreeSet;

import org.apache.hadoop.hbase.util.Bytes;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

class MyByteArraySet extends TreeSet<byte[]> {

    public MyByteArraySet() {
        super(Bytes.BYTES_COMPARATOR);
    }
}

public static void main(String[] args) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    NavigableSet<byte[]> set = new TreeSet<>(Bytes.BYTES_COMPARATOR);
    set.add(Bytes.toBytes("ROW1"));
    set.add(Bytes.toBytes("ROW2"));
    set.add(Bytes.toBytes("ROW3"));
    byte[] array = mapper.writeValueAsBytes(set);
    NavigableSet<byte[]> result = mapper.readValue(array,
            new TypeReference<MyByteArraySet>() {
            });

    for (byte[] value : result) {
        System.out.println(Bytes.toString(value));
    }
}
}

Any idea about this?

@cowtowncoder
Copy link
Member

Oh that's simple. Your inner class must have static qualifier: otherwise it can not be constructed without implicit "this" argument. So either create it as main-level class (no static used), or add static in there.

@kdkeck
Copy link

kdkeck commented Jul 30, 2014

Related issue: #509.

@cowtowncoder
Copy link
Member

Actually I think that #1399 is a solution for this: this does require setting of initial value (and either global default for merging, or per-property annotation), but should work fine.
Will close as I do not have a better general-purpose solution in mind; custom deserializer would be next logical choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants