diff --git a/lib/json-string-packer.js b/lib/json-string-packer.js new file mode 100644 index 0000000..dcbac43 --- /dev/null +++ b/lib/json-string-packer.js @@ -0,0 +1,94 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +var createPromiseCallback = require('./utils').createPromiseCallback; + +module.exports = JSONStringPacker; + +var ISO_DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/; + +/** + * Create a new Packer instance that can be used to convert between JavaScript + * objects and a JsonString representation in a String. + * + * @param {String} encoding Buffer encoding refer to https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings + */ +function JSONStringPacker(encoding) { + this.encoding = encoding || 'base64'; +} + +/** + * Encode the provided value to a `JsonString`. + * + * @param {*} value Any value (string, number, object) + * @callback {Function} cb The callback to receive the parsed result. + * @param {Error} err + * @param {Buffer} data The encoded value + * @promise + */ +JSONStringPacker.prototype.encode = function(value, cb) { + var encoding = this.encoding; + + cb = cb || createPromiseCallback(); + try { + var data = JSON.stringify(value, function(key, value) { + if (Buffer.isBuffer(this[key])) { + return { + type: 'Buffer', + data: this[key].toString(encoding), + }; + } else { + return value; + } + }); + + setImmediate(function() { + cb(null, data); + }); + } catch (err) { + setImmediate(function() { + cb(err); + }); + } + return cb.promise; +}; + +/** + * Decode the JsonString value back to a JavaScript value. + * @param {String} jsonString The JsonString input. + * @callback {Function} cb The callback to receive the composed value. + * @param {Error} err + * @param {*} value Decoded value. + * @promise + */ +JSONStringPacker.prototype.decode = function(jsonString, cb) { + var encoding = this.encoding; + + cb = cb || createPromiseCallback(); + try { + var value = JSON.parse(jsonString, function(k, v) { + if (v && v.type && v.type === 'Buffer') { + return new Buffer(v.data, encoding); + } + + if (ISO_DATE_REGEXP.exec(v)) { + return new Date(v); + } + + return v; + }); + + setImmediate(function() { + cb(null, value); + }); + } catch (err) { + setImmediate(function() { + cb(err); + }); + } + return cb.promise; +}; diff --git a/test/json-string-packer.test.js b/test/json-string-packer.test.js new file mode 100644 index 0000000..0554654 --- /dev/null +++ b/test/json-string-packer.test.js @@ -0,0 +1,72 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-connector +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +var JSONStringPacker = require('../lib/json-string-packer'); +var expect = require('chai').expect; + +describe('JSONStringPacker', function() { + var packer; + + beforeEach(function createPacker() { + packer = new JSONStringPacker(); + }); + + describe('encode()', function() { + it('supports invocation with a callback', function(done) { + packer.encode('a-value', done); + }); + }); + + describe('decode()', function() { + it('supports invocation with a callback', function(done) { + packer.encode('a-value', function(err, jsonString) { + if (err) return done(err); + packer.decode(jsonString, function(err, result) { + if (err) return done(err); + expect(result).to.eql('a-value'); + done(); + }); + }); + }); + }); + + describe('roundtrip', function() { + var TEST_CASES = { + String: 'a-value', + Object: { a: 1, b: 2 }, + Buffer: new Buffer([1, 2, 3]), + Date: new Date('2016-08-03T11:53:03.470Z'), + Integer: 12345, + Float: 12.345, + Boolean: false, + }; + + Object.keys(TEST_CASES).forEach(function(tc) { + it('works for ' + tc + ' values', function() { + var value = TEST_CASES[tc]; + return encodeAndDecode(value) + .then(function(result) { + expect(result).to.eql(value); + }); + }); + }); + + it('works for nested properties', function() { + return encodeAndDecode(TEST_CASES) + .then(function(result) { + expect(result).to.eql(TEST_CASES); + }); + }); + + function encodeAndDecode(value) { + return packer.encode(value) + .then(function(binary) { + return packer.decode(binary); + }); + } + }); +});