hildjj / node-cbor

Encode and decode CBOR documents, with both easy mode, streaming mode, and SAX-style evented mode.
MIT License
357 stars 73 forks source link

Encode some arrays as indefinite length #107

Closed v-almonacid closed 4 years ago

v-almonacid commented 4 years ago

Is is possible to enforce the encoder to use indefinite-length arrays?

What I want to achieve is for instance:

const a = [[0, 1]]

and after encoding, obtain:

9F       # array(*)
   82    # array(2)
      00 # unsigned(0)
      01 # unsigned(1)
   FF    # primitive(*)

Note that the outer array has indefinite-length while the inner array has a fixed length.

hildjj commented 4 years ago

What would you think about having stream.Readable's encoded indefinitely, with either an Array (if the stream is in object mode) or a byte string (if not in object mode). I can't think of how to cause indefinite maps or indefinite strings, but neither of those have interesting use cases that I can think of.

Once I get something working for the above, I bet something will become apparent for those cases if someone wants them.

v-almonacid commented 4 years ago

It sounds good to me but honestly I don't know much about how things are implemented.

I need to encode a rather simple data structure of the form: x = [ [ [ [Array], [Array] ], [ [Array] ] ] ]. Right now it works almost perfectly just using cbor.encode(x), the only detail is that a few arrays within x need to be 9F...FF

hildjj commented 4 years ago

I figured out a work-around for you:

function encodeArrayIndefinite(gen, obj) {
  if (obj == null) {
    obj = this
  }
  let ret = gen.push(Buffer.from([0x9f])) // indefinite array
  for (const e of obj) {
    ret = ret && gen.pushAny(e)
  }
  ret = ret && gen.push(Buffer.from([0xff])) // done
  return ret
}

const bytes = cbor.encodeOne([1, "a", false, Infinity, []], {
  genTypes: [
    Array, encodeArrayIndefinite
  ]
})
hildjj commented 4 years ago

If you only want to encode some arrays that way, you could do this:

const a = [1, "a", false, Infinity, []]
a.encodeCBOR = encodeArrayIndefinite
ashisherc commented 4 years ago

I figured out a work-around for you:

function encodeArrayIndefinite(gen, obj) {
  if (obj == null) {
    obj = this
  }
  let ret = gen.push(Buffer.from([0x9f])) // indefinite array
  for (const e of obj) {
    ret = ret && gen.pushAny(e)
  }
  ret = ret && gen.push(Buffer.from([0xff])) // done
  return ret
}

const bytes = cbor.encodeOne([1, "a", false, Infinity, []], {
  genTypes: [
    Array, encodeArrayIndefinite
  ]
})

can you please also help with a workaround to encode an indefinite map as well!!

v-almonacid commented 4 years ago

I figured out a work-around for you:

function encodeArrayIndefinite(gen, obj) {
  if (obj == null) {
    obj = this
  }
  let ret = gen.push(Buffer.from([0x9f])) // indefinite array
  for (const e of obj) {
    ret = ret && gen.pushAny(e)
  }
  ret = ret && gen.push(Buffer.from([0xff])) // done
  return ret
}

const bytes = cbor.encodeOne([1, "a", false, Infinity, []], {
  genTypes: [
    Array, encodeArrayIndefinite
  ]
})

I really appreciate that you took the time to look into it, thanks! I ended up just using an ugly hack, but your workaround looks much better.

hildjj commented 4 years ago

I've added Encoder.encodeIndefinite with an example in the docs. Should work for Array, Map, String, Buffer, and Objects. I'm going to fix a thing or too more before doing a release, hopefully in a few hours, but take a look at the tests in the above commit for some ideas on how to use it.

janpoltan commented 3 years ago

cbor.Encoder.encodeIndefinite returns undefined

hildjj commented 3 years ago

I think I haven't done a release with encodeIndefinite in it. I'll get on that in the next day or two.

janpoltan commented 3 years ago

It would be great that you will do a new release with the indefinite lengths for the array and map.

hildjj commented 3 years ago

Sorry, I finally got around to a release. Try 5.1.1, and file new bugs as needed.