Search code examples
javascriptarraybuffercbor

cbor.js decodes BYTES to associative object instead of Array/TypedArray


I have a CBOR payload containing a MAP with a BYTES field:

// simplified testcase:
var data = [0xBF, 0x63, 0x72, 0x61, 0x77, 0x58, 0x10, 0x00,
            0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF]

CBOR diagnostic:

BF                                     # map(*)
   63                                  # text(3)
      726177                           # "raw"
   58 10                               # bytes(16)
      00FF00FF0000FFFFFFFFFFFF000000FF # [16 bytes]
   FF                                  # end of map(*)

or, in standard-ish object notation: {"raw": h'00FF00FF0000FFFFFFFFFFFF000000FF'}

When I decode the above payload in JavaScript (using cbor.js):

// prepare an ArrayBuffer to pass to CBOR.decode:
var buf = new ArrayBuffer(data.length)
var bi = new Int8Array(buf)
for(var i = 0; i < data.length; i++) bi[i] = data[i]

var decoded = CBOR.decode(buf)
console.log('decoded:', JSON.stringify(decoded))
console.log('isArray:', Array.isArray(decoded.raw))

the "raw" field is not a proper Array (for which Array.isArray(...) would return true), but an "objectified" version of such array:

decoded: {"raw":{"0":0,"1":255,"2":0,"3":255,"4":0,"5":0,"6":255,"7":255,"8":255,"9":255,"10":255,"11":255,"12":0,"13":0,"14":0,"15":255}}

isArray: false

this causes me some problem, as later I need a proper Array (or TypedArray) type to pass to the QML JavaScript engine, and that "objectified" object is not accepted; I have to manually convert to a proper array:

if(!Array.isArray(texture.raw)) {
    console.warn(`Texture data is not an array. Conversion will be slow!`)
    var data = new Array(Object.keys(texture.raw).length)
    for(var k in texture.raw)
        data[k] = texture.raw[k]
    root.textureData = data
} else {
    root.textureData = texture.raw
}

but that is very slow.

Is there anything better that can be done? e.g. a faster conversion? an instruction to cbor.js to decode it properly? something else?


Solution

  • You can add a "length" property and then turn it into a real array with Array.from():

    if(!Array.isArray(texture.raw)) {
        const rlen = Object.keys(texture.raw).length;
        texture.raw.length = rlen;
        root.textureData = Array.from(texture.raw);
    } 
    else {
        root.textureData = texture.raw
    }
    

    Note that the above does basically the same thing as your code, but it won't be slow unless that "raw" stuff is megabytes in size. (In that case, whatever you're doing with it will be coping with that size also.)