Search code examples
javagwt

How to convert ArrayBuffer to byte[]?


I'm reading a file from a FileList retrieved from HTMLInputElement and want to get the raw bytes out of File#arrayBuffer(). How do I do it?

import elemental2.core.ArrayBuffer;

public static byte[] toBytes(ArrayBuffer buffer) {
    // code in question
}


Solution

  • It turns out that as long as you don't look too closely at it, a JS Int8Array (and so a elemental2.core.Int8Array) behaves like you'd expect a GWT/Java byte[] to do - it will only contain values from -127 to 128. Unlike a "real" byte[], it will not correctly cast to byte[] and anything calling .getClass() or other Java methods on it will not work correctly either. It also can't be cast (by generics or explicitly) to byte[] either.

    So if you need to treat the contents as a real Java array, you have to copy it first. Your answer will do what you want, but it might be more clearly correct to not "convert it from Double" along the way:

    import elemental2.core.ArrayBuffer;
    import elemental2.core.Int8Array;
    import jsinterop.base.Js;
    
    public static byte[] toBytes(ArrayBuffer buffer) {
        // Use uncheckedCast since we are outright lying to the compiler
        byte[] arr = Js.uncheckedCast(new Int8Array(buffer));
        byte[] result = new byte[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result[i] = arr[i];
        }
        return result;
    }
    

    Your solution is also correct - a java.lang.Double in GWT and J2CL is exactly the same as a JS Number type, which is what your Int8Array is going to return when you query its values. The byteValue() call you're making will ask the compiler to make certain that the values are what is expected - it will truncate (probably via value & 0xFF?) the value, which should be cheap, but not entirely free.

    Finally as long as you are just going to treat the resulting byte[] as a collection you can iterate, and won't be dealing with it in some way that the actual type will be checked, we can get away with not copying it at all:

    import elemental2.core.ArrayBuffer;
    import elemental2.core.Int8Array;
    import jsinterop.base.Js;
    
    public static byte[] toBytes(ArrayBuffer buffer) {
        byte[] result = Js.uncheckedCast(new Int8Array(buffer));
        return result;
    }
    

    This really is cheating though, and has other downsides, like if the original buffer is modified the array will be changed too, and vice versa. But it is very fast!