From a1bd349a6eb382737716ec1fc6c1061e710358d1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 11 Sep 2023 13:42:22 +1000 Subject: [PATCH] feat: add decodeFirst to json decoder --- README.md | 2 +- lib/json/decode.js | 14 ++++++++++++-- lib/json/json.js | 4 ++-- test/test-json.js | 30 +++++++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 84c1de6..1fc5a3c 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,7 @@ There are a number of forms where an object will not round-trip precisely, if th **cborg** can also encode and decode JSON using the same pipeline and many of the same settings. For most (but not all) cases it will be faster to use `JSON.parse()` and `JSON.stringify()`, however **cborg** provides much more control over the process to handle determinism and be more restrictive in allowable forms. It also operates natively with Uint8Arrays rather than strings which may also offer some minor efficiency or usability gains in some circumstances. -Use `import { encode, decode } from 'cborg/json'` to access the JSON handling encoder and decoder. +Use `import { encode, decode, decodeFirst } from 'cborg/json'` to access the JSON handling encoder and decoder. Many of the same encode and decode options available for CBOR can be used to manage JSON handling. These include strictness requirements for decode and custom tag encoders for encode. Tag encoders can't create new tags as there are no tags in JSON, but they can replace JavaScript object forms with custom JSON forms (e.g. convert a `Uint8Array` to a valid JSON form rather than having the encoder throw an error). The inverse is also possible, turning specific JSON forms into JavaScript forms, by using a custom tokenizer on decode. diff --git a/lib/json/decode.js b/lib/json/decode.js index 2a324de..41fb760 100644 --- a/lib/json/decode.js +++ b/lib/json/decode.js @@ -1,4 +1,4 @@ -import { decode as _decode } from '../decode.js' +import { decode as _decode, decodeFirst as _decodeFirst } from '../decode.js' import { Token, Type } from '../token.js' import { decodeCodePointsArray } from '../byte-utils.js' import { decodeErrPrefix } from '../common.js' @@ -447,4 +447,14 @@ function decode (data, options) { return _decode(data, options) } -export { decode, Tokenizer } +/** + * @param {Uint8Array} data + * @param {DecodeOptions} [options] + * @returns {[any, Uint8Array]} + */ +function decodeFirst (data, options) { + options = Object.assign({ tokenizer: new Tokenizer(data, options) }, options) + return _decodeFirst(data, options) +} + +export { decode, decodeFirst, Tokenizer } diff --git a/lib/json/json.js b/lib/json/json.js index 703d423..9fc1474 100644 --- a/lib/json/json.js +++ b/lib/json/json.js @@ -1,4 +1,4 @@ import { encode } from './encode.js' -import { decode, Tokenizer } from './decode.js' +import { decode, decodeFirst, Tokenizer } from './decode.js' -export { encode, decode, Tokenizer } +export { encode, decode, decodeFirst, Tokenizer } diff --git a/test/test-json.js b/test/test-json.js index 7b90b71..7962457 100644 --- a/test/test-json.js +++ b/test/test-json.js @@ -1,7 +1,7 @@ /* eslint-env mocha,es2020 */ import { assert } from 'chai' -import { decode, encode } from 'cborg/json' +import { decode, decodeFirst, encode } from 'cborg/json' const toBytes = (str) => new TextEncoder().encode(str) @@ -188,4 +188,32 @@ describe('json basics', () => { assert.deepStrictEqual(decode(toBytes('{"foo":1,"foo":2}')), { foo: 2 }) assert.throws(() => decode(toBytes('{"foo":1,"foo":2}'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/) }) + + it('decodeFirst', () => { + /* + const encoded = new TextDecoder().decode(encode(obj, sorting === false ? { mapSorter: null } : undefined)) + const json = JSON.stringify(obj) + assert.strictEqual(encoded, json) + const decoded = decode(toBytes(JSON.stringify(obj))) + assert.deepStrictEqual(decoded, obj) + */ + let buf = new TextEncoder().encode('{"foo":1,"bar":2}1"ping"2null3[1,2,3]""[[],[],{"boop":true}]') + const expected = [ + { foo: 1, bar: 2 }, + 1, + 'ping', + 2, + null, + 3, + [1, 2, 3], + '', + [[], [], { boop: true }] + ] + for (const exp of expected) { + const [obj, rest] = decodeFirst(buf) + assert.deepStrictEqual(exp, obj) + buf = rest + } + assert.strictEqual(buf.length, 0) + }) })