diff --git a/dist/jsonifier.js b/dist/jsonifier.js index f1d9689..4b6c2ce 100644 --- a/dist/jsonifier.js +++ b/dist/jsonifier.js @@ -2,6 +2,17 @@ const _ = require('lodash'); +/** + * Converts object to only contain static data + * + * Our state data either has static leaf values or functions which yield static + * values. This converst the entire object to only contain static data (subject + * to users creating functions which _only_ yield static output) + * + * @method compiler + * @param {Object} obj Typically jsonifier state object + * @return {Object} + */ function compiler(obj) { if (_.isFunction(obj)) { return obj(); @@ -13,6 +24,11 @@ function compiler(obj) { return obj; } +/** + * Converts generators to functions which yield next value + * + * See [_.assignWith](https://lodash.com/docs#assignInWith) + */ function dynamicCustomiser(objValue, srcValue) { if (_.isFunction(srcValue)) { let result = srcValue(); @@ -23,8 +39,48 @@ function dynamicCustomiser(objValue, srcValue) { return srcValue; } +/** + * Extends an object to make sure it has child attributes + * + * @method createObject + * @param {Object} srcObj The target object + * @param {String} ref Period separated description: + * a.b.c => {a: {b: {c: {}}}} + * @param {Object} finalValue [optional] final value to assign to leaf node: + * a.b.c (final 'foo') => {a: {b: {c: 'foo'}}} + * @return {Object Ref} The final objects 'a.b.c' => {c: {}} + */ +function createObject(srcObj, ref, finalValue) { + let steps = ref.split('.'); + + steps.forEach((attrib, index) => { + srcObj[attrib] = srcObj.hasOwnProperty(attrib) && _.isObject(srcObj[attrib]) ? srcObj[attrib] : {}; + if (index < steps.length - 1) { + srcObj = srcObj[attrib]; + } else { + srcObj = srcObj[attrib] = finalValue; + } + }); + + return srcObj; +} + +/** + * Captures object of JSON data in particular states + * @class {JSONifier} + */ module.exports = class JSONifier { + /** + * @method constructor + * @param {JSONifier} state A JSONifier instance to inherit properties from + * + * @param {Object} ops Optional parameters + * @param {String} ops.namespace Period separated namespace: 'a.b.c' => {'a': {'b': 'c':{}}} + * @param {Integer} ops.limit Limit the number of responses from this instance [default: unlimited] + * @param {Function} ops.compiler Change the way an instance builds JSON object {@link compiler} + * @return {JSONifier} + */ constructor(state, ops) { this.options = { namespace: undefined, @@ -40,28 +96,21 @@ module.exports = class JSONifier { _.extend(this.options, state); } - if (state instanceof JSONifier) { - this._state = _.cloneDeep(state._state); - } else { - this._state = {}; - } + this._state = state instanceof JSONifier ? _.cloneDeep(state._state) : {}; - this._current = _.isUndefined(this.options.namespace) ? this._state : this._state[this.options.namespace] = this._state[this.options.namespace] || {}; + this._current = _.isUndefined(this.options.namespace) ? this._state : createObject(this._state, this.options.namespace, {}); } + /** + * Adds an enhanced 'JSON object' to the current instance + * + * @method add + * @param {String} method [optional] Dot notation: e.g. 'a' or 'a.b.c.d' + * @param {Object|Function|Generator} Yield static data (object, string, number, etc...) + */ add(method, generator) { if (_.isString(method)) { - let obj = this._current; - - let steps = method.split('.'); - const final_step = steps.pop(); - - steps.forEach(attrib => { - obj[attrib] = obj.hasOwnProperty(attrib) && _.isObject(obj[attrib]) ? obj[attrib] : {}; - obj = obj[attrib]; - }); - - obj[final_step] = _.assignWith(generator, generator, dynamicCustomiser); + createObject(this._current, method, _.assignWith(generator, generator, dynamicCustomiser)); return this; } @@ -73,6 +122,11 @@ module.exports = class JSONifier { throw Error('Illegal use of jsonifier#add'); } + /** + * Yields static JSON objects from all inherited instances + * @method build + * @return {Generator} which yields JSON objects + */ build() { let that = this; return function* iterableJSONifier() { @@ -82,4 +136,4 @@ module.exports = class JSONifier { }(); } }; -//# sourceMappingURL=data:application/json;base64,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bUIsRUFBRSxVQUFGLENBQWEsU0FBYixFQUF3QixTQUF4QixFQUFtQyxpQkFBbkMsQ0FBbkIsQ0Fkb0I7QUFlcEIsbUJBQU8sSUFBUCxDQWZvQjtTQUF4Qjs7QUFrQkEsWUFBSSxFQUFFLFdBQUYsQ0FBYyxTQUFkLEtBQTRCLEVBQUUsUUFBRixDQUFXLE1BQVgsQ0FBNUIsRUFBZ0Q7QUFDaEQsY0FBRSxVQUFGLENBQWEsS0FBSyxRQUFMLEVBQWUsTUFBNUIsRUFBb0MsaUJBQXBDLEVBRGdEO0FBRWhELG1CQUFPLElBQVAsQ0FGZ0Q7U0FBcEQ7O0FBS0EsY0FBTSxNQUFNLDhCQUFOLENBQU4sQ0F4Qm1CO0tBQXZCOztBQTJCQSxZQUFRO0FBQ0osWUFBSSxPQUFPLElBQVAsQ0FEQTtBQUVKLGVBQU8sVUFBVSxpQkFBVixHQUE4QjtBQUNqQyxpQkFBSSxJQUFJLElBQUUsQ0FBRixFQUFLLEtBQUssS0FBSyxPQUFMLENBQWEsS0FBYixFQUFvQixJQUFJLENBQUMsSUFBSSxDQUFKLENBQUQsR0FBVSxPQUFPLGdCQUFQLEVBQXlCO0FBQ3pFLHNCQUFNLEtBQUssT0FBTCxDQUFhLFFBQWIsQ0FBc0IsS0FBSyxNQUFMLENBQTVCLENBRHlFO2FBQTdFO1NBREcsRUFBUCxDQUZJO0tBQVI7Q0F4RGEiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5jb25zdCBfID0gcmVxdWlyZSgnbG9kYXNoJyk7XG5cbmZ1bmN0aW9uIGNvbXBpbGVyKG9iaikge1xuICAgIGlmIChfLmlzRnVuY3Rpb24ob2JqKSkge1xuICAgICAgICByZXR1cm4gb2JqKCk7XG4gICAgfSBlbHNlIGlmIChfLmlzT2JqZWN0KG9iaikpIHtcbiAgICAgICAgbGV0IHJlc3VsdCA9IHt9O1xuICAgICAgICBPYmplY3Qua2V5cyhvYmopLmZvckVhY2goa2V5ID0+IHJlc3VsdFtrZXldID0gY29tcGlsZXIob2JqW2tleV0pKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbn1cblxuZnVuY3Rpb24gZHluYW1pY0N1c3RvbWlzZXIob2JqVmFsdWUsIHNyY1ZhbHVlKSB7XG4gICAgaWYgKF8uaXNGdW5jdGlvbihzcmNWYWx1ZSkpIHtcbiAgICAgICAgbGV0IHJlc3VsdCA9IHNyY1ZhbHVlKCk7XG4gICAgICAgIGlmIChyZXN1bHQudG9TdHJpbmcoKSA9PT0gJ1tvYmplY3QgR2VuZXJhdG9yXScpIHtcbiAgICAgICAgICAgIHNyY1ZhbHVlID0gKCkgPT4gcmVzdWx0Lm5leHQoKS52YWx1ZTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gc3JjVmFsdWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY2xhc3MgSlNPTmlmaWVyIHtcblxuICAgIGNvbnN0cnVjdG9yKHN0YXRlLCBvcHMpIHtcbiAgICAgICAgdGhpcy5vcHRpb25zID0ge1xuICAgICAgICAgICAgbmFtZXNwYWNlOiB1bmRlZmluZWQsXG4gICAgICAgICAgICBsaW1pdDogLTEsXG4gICAgICAgICAgICBjb21waWxlcjogY29tcGlsZXJcbiAgICAgICAgfTtcblxuICAgICAgICBpZiAoc3RhdGUgaW5zdGFuY2VvZiBKU09OaWZpZXIgJiYgXy5pc09iamVjdChvcHMpKSB7XG4gICAgICAgICAgICBfLmV4dGVuZCh0aGlzLm9wdGlvbnMsIG9wcyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoXy5pc09iamVjdChzdGF0ZSkgJiYgXy5pc1VuZGVmaW5lZChvcHMpKSB7XG4gICAgICAgICAgICBfLmV4dGVuZCh0aGlzLm9wdGlvbnMsIHN0YXRlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzdGF0ZSBpbnN0YW5jZW9mIEpTT05pZmllcikge1xuICAgICAgICAgICAgdGhpcy5fc3RhdGUgPSBfLmNsb25lRGVlcChzdGF0ZS5fc3RhdGUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fc3RhdGUgPSB7fTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX2N1cnJlbnQgPSBfLmlzVW5kZWZpbmVkKHRoaXMub3B0aW9ucy5uYW1lc3BhY2UpXG4gICAgICAgICAgICA/IHRoaXMuX3N0YXRlXG4gICAgICAgICAgICA6IHRoaXMuX3N0YXRlW3RoaXMub3B0aW9ucy5uYW1lc3BhY2VdID0gdGhpcy5fc3RhdGVbdGhpcy5vcHRpb25zLm5hbWVzcGFjZV0gfHwge31cbiAgICAgICAgICAgIDtcbiAgICB9XG5cbiAgICBhZGQobWV0aG9kLCBnZW5lcmF0b3IpIHtcbiAgICAgICAgaWYgKF8uaXNTdHJpbmcobWV0aG9kKSkge1xuICAgICAgICAgICAgbGV0IG9iaiA9IHRoaXMuX2N1cnJlbnQ7XG5cbiAgICAgICAgICAgIGxldCBzdGVwcyA9IG1ldGhvZC5zcGxpdCgnLicpO1xuICAgICAgICAgICAgY29uc3QgZmluYWxfc3RlcCA9IHN0ZXBzLnBvcCgpO1xuXG4gICAgICAgICAgICBzdGVwcy5mb3JFYWNoKGF0dHJpYiA9PiB7XG4gICAgICAgICAgICAgICAgb2JqW2F0dHJpYl0gPSBvYmouaGFzT3duUHJvcGVydHkoYXR0cmliKSAmJiBfLmlzT2JqZWN0KG9ialthdHRyaWJdKVxuICAgICAgICAgICAgICAgICAgICA/IG9ialthdHRyaWJdXG4gICAgICAgICAgICAgICAgICAgIDoge31cbiAgICAgICAgICAgICAgICAgICAgO1xuICAgICAgICAgICAgICAgIG9iaiA9IG9ialthdHRyaWJdO1xuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIG9ialtmaW5hbF9zdGVwXSAgPSBfLmFzc2lnbldpdGgoZ2VuZXJhdG9yLCBnZW5lcmF0b3IsIGR5bmFtaWNDdXN0b21pc2VyKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKF8uaXNVbmRlZmluZWQoZ2VuZXJhdG9yKSAmJiBfLmlzT2JqZWN0KG1ldGhvZCkpIHtcbiAgICAgICAgICAgIF8uYXNzaWduV2l0aCh0aGlzLl9jdXJyZW50LCBtZXRob2QsIGR5bmFtaWNDdXN0b21pc2VyKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhyb3cgRXJyb3IoJ0lsbGVnYWwgdXNlIG9mIGpzb25pZmllciNhZGQnKTtcbiAgICB9XG5cbiAgICBidWlsZCgpIHtcbiAgICAgICAgbGV0IHRoYXQgPSB0aGlzO1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24qIGl0ZXJhYmxlSlNPTmlmaWVyKCkge1xuICAgICAgICAgICAgZm9yKGxldCBpPTA7IGkgIT0gdGhhdC5vcHRpb25zLmxpbWl0OyBpID0gKGkgKyAxKSAlIE51bWJlci5NQVhfU0FGRV9JTlRFR0VSKSB7XG4gICAgICAgICAgICAgICAgeWllbGQgdGhhdC5vcHRpb25zLmNvbXBpbGVyKHRoYXQuX3N0YXRlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSgpO1xuICAgIH1cbn07XG4iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= +//# sourceMappingURL=data:application/json;base64,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QixPQTZDN0IsQ0FBSSxNQUFKLEVBQVksU0FBWixFQUF1QjtBQUNuQixZQUFJLEVBQUUsUUFBRixDQUFXLE1BQVgsQ0FBSixFQUF3QjtBQUNwQix5QkFBYSxLQUFLLFFBQUwsRUFBZSxNQUE1QixFQUFvQyxFQUFFLFVBQUYsQ0FBYSxTQUFiLEVBQXdCLFNBQXhCLEVBQW1DLGlCQUFuQyxDQUFwQyxFQURvQjtBQUVwQixtQkFBTyxJQUFQLENBRm9CO1NBQXhCOztBQUtBLFlBQUksRUFBRSxXQUFGLENBQWMsU0FBZCxLQUE0QixFQUFFLFFBQUYsQ0FBVyxNQUFYLENBQTVCLEVBQWdEO0FBQ2hELGNBQUUsVUFBRixDQUFhLEtBQUssUUFBTCxFQUFlLE1BQTVCLEVBQW9DLGlCQUFwQyxFQURnRDtBQUVoRCxtQkFBTyxJQUFQLENBRmdEO1NBQXBEOztBQUtBLGNBQU0sTUFBTSw4QkFBTixDQUFOLENBWG1CO0tBQXZCOzs7Ozs7O0FBN0M2QixTQWdFN0IsR0FBUTtBQUNKLFlBQUksT0FBTyxJQUFQLENBREE7QUFFSixlQUFPLFVBQVUsaUJBQVYsR0FBOEI7QUFDakMsaUJBQUksSUFBSSxJQUFFLENBQUYsRUFBSyxLQUFLLEtBQUssT0FBTCxDQUFhLEtBQWIsRUFBb0IsSUFBSSxDQUFDLElBQUksQ0FBSixDQUFELEdBQVUsT0FBTyxnQkFBUCxFQUF5QjtBQUN6RSxzQkFBTSxLQUFLLE9BQUwsQ0FBYSxRQUFiLENBQXNCLEtBQUssTUFBTCxDQUE1QixDQUR5RTthQUE3RTtTQURHLEVBQVAsQ0FGSTtLQUFSO0NBaEVhIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnO1xuY29uc3QgXyA9IHJlcXVpcmUoJ2xvZGFzaCcpO1xuXG4vKipcbiAqIENvbnZlcnRzIG9iamVjdCB0byBvbmx5IGNvbnRhaW4gc3RhdGljIGRhdGFcbiAqXG4gKiBcdE91ciBzdGF0ZSBkYXRhIGVpdGhlciBoYXMgc3RhdGljIGxlYWYgdmFsdWVzIG9yIGZ1bmN0aW9ucyB3aGljaCB5aWVsZCBzdGF0aWNcbiAqIFx0dmFsdWVzLiAgVGhpcyBjb252ZXJzdCB0aGUgZW50aXJlIG9iamVjdCB0byBvbmx5IGNvbnRhaW4gc3RhdGljIGRhdGEgKHN1YmplY3RcbiAqIFx0dG8gdXNlcnMgY3JlYXRpbmcgZnVuY3Rpb25zIHdoaWNoIF9vbmx5XyB5aWVsZCBzdGF0aWMgb3V0cHV0KVxuICpcbiAqIEBtZXRob2QgY29tcGlsZXJcbiAqIEBwYXJhbSAge09iamVjdH0gb2JqIFR5cGljYWxseSBqc29uaWZpZXIgc3RhdGUgb2JqZWN0XG4gKiBAcmV0dXJuIHtPYmplY3R9XG4gKi9cbmZ1bmN0aW9uIGNvbXBpbGVyKG9iaikge1xuICAgIGlmIChfLmlzRnVuY3Rpb24ob2JqKSkge1xuICAgICAgICByZXR1cm4gb2JqKCk7XG4gICAgfSBlbHNlIGlmIChfLmlzT2JqZWN0KG9iaikpIHtcbiAgICAgICAgbGV0IHJlc3VsdCA9IHt9O1xuICAgICAgICBPYmplY3Qua2V5cyhvYmopLmZvckVhY2goa2V5ID0+IHJlc3VsdFtrZXldID0gY29tcGlsZXIob2JqW2tleV0pKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbn1cblxuXG4vKipcbiAqIENvbnZlcnRzIGdlbmVyYXRvcnMgdG8gZnVuY3Rpb25zIHdoaWNoIHlpZWxkIG5leHQgdmFsdWVcbiAqXG4gKiBcdFNlZSBbXy5hc3NpZ25XaXRoXShodHRwczovL2xvZGFzaC5jb20vZG9jcyNhc3NpZ25JbldpdGgpXG4gKi9cbmZ1bmN0aW9uIGR5bmFtaWNDdXN0b21pc2VyKG9ialZhbHVlLCBzcmNWYWx1ZSkge1xuICAgIGlmIChfLmlzRnVuY3Rpb24oc3JjVmFsdWUpKSB7XG4gICAgICAgIGxldCByZXN1bHQgPSBzcmNWYWx1ZSgpO1xuICAgICAgICBpZiAocmVzdWx0LnRvU3RyaW5nKCkgPT09ICdbb2JqZWN0IEdlbmVyYXRvcl0nKSB7XG4gICAgICAgICAgICBzcmNWYWx1ZSA9ICgpID0+IHJlc3VsdC5uZXh0KCkudmFsdWU7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHNyY1ZhbHVlO1xufVxuXG5cbi8qKlxuICogRXh0ZW5kcyBhbiBvYmplY3QgdG8gbWFrZSBzdXJlIGl0IGhhcyBjaGlsZCBhdHRyaWJ1dGVzXG4gKlxuICogQG1ldGhvZCBjcmVhdGVPYmplY3RcbiAqIEBwYXJhbSAge09iamVjdH0gICAgIHNyY09iaiAgICAgIFRoZSB0YXJnZXQgb2JqZWN0XG4gKiBAcGFyYW0gIHtTdHJpbmd9ICAgICByZWYgICAgICAgICBQZXJpb2Qgc2VwYXJhdGVkIGRlc2NyaXB0aW9uOlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXHRhLmIuYyA9PiB7YToge2I6IHtjOiB7fX19fVxuICogQHBhcmFtICB7T2JqZWN0fSAgICAgZmluYWxWYWx1ZSAgW29wdGlvbmFsXSBmaW5hbCB2YWx1ZSB0byBhc3NpZ24gdG8gbGVhZiBub2RlOlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXHRhLmIuYyAoZmluYWwgJ2ZvbycpID0+IHthOiB7Yjoge2M6ICdmb28nfX19XG4gKiBAcmV0dXJuIHtPYmplY3QgUmVmfSBUaGUgZmluYWwgb2JqZWN0cyAnYS5iLmMnID0+IHtjOiB7fX1cbiAqL1xuZnVuY3Rpb24gY3JlYXRlT2JqZWN0KHNyY09iaiwgcmVmLCBmaW5hbFZhbHVlKSB7XG4gICAgbGV0IHN0ZXBzID0gcmVmLnNwbGl0KCcuJyk7XG5cbiAgICBzdGVwcy5mb3JFYWNoKChhdHRyaWIsIGluZGV4KSA9PiB7XG4gICAgICAgIHNyY09ialthdHRyaWJdID0gc3JjT2JqLmhhc093blByb3BlcnR5KGF0dHJpYikgJiYgXy5pc09iamVjdChzcmNPYmpbYXR0cmliXSlcbiAgICAgICAgICAgID8gc3JjT2JqW2F0dHJpYl1cbiAgICAgICAgICAgIDoge31cbiAgICAgICAgICAgIDtcbiAgICAgICAgaWYgKGluZGV4IDwgc3RlcHMubGVuZ3RoIC0gMSkge1xuICAgICAgICAgICAgc3JjT2JqID0gc3JjT2JqW2F0dHJpYl07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzcmNPYmogPSBzcmNPYmpbYXR0cmliXSA9IGZpbmFsVmFsdWU7XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgIHJldHVybiBzcmNPYmo7XG59XG5cblxuLyoqXG4gKiBDYXB0dXJlcyBvYmplY3Qgb2YgSlNPTiBkYXRhIGluIHBhcnRpY3VsYXIgc3RhdGVzXG4gKiBAY2xhc3Mge0pTT05pZmllcn1cbiAqL1xubW9kdWxlLmV4cG9ydHMgPSBjbGFzcyBKU09OaWZpZXIge1xuXG4gICAgLyoqXG4gICAgICogQG1ldGhvZCBjb25zdHJ1Y3RvclxuICAgICAqIEBwYXJhbSAge0pTT05pZmllcn0gICAgICBzdGF0ZSAgIEEgSlNPTmlmaWVyIGluc3RhbmNlIHRvIGluaGVyaXQgcHJvcGVydGllcyBmcm9tXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9ICAgICAgICAgb3BzICAgICAgICAgICAgIE9wdGlvbmFsIHBhcmFtZXRlcnNcbiAgICAgKiBAcGFyYW0gIHtTdHJpbmd9ICAgICAgICAgb3BzLm5hbWVzcGFjZSAgIFBlcmlvZCBzZXBhcmF0ZWQgbmFtZXNwYWNlOiAnYS5iLmMnID0+IHsnYSc6IHsnYic6ICdjJzp7fX19XG4gICAgICogQHBhcmFtICB7SW50ZWdlcn0gICAgICAgIG9wcy5saW1pdCAgICAgICBMaW1pdCB0aGUgbnVtYmVyIG9mIHJlc3BvbnNlcyBmcm9tIHRoaXMgaW5zdGFuY2UgW2RlZmF1bHQ6IHVubGltaXRlZF1cbiAgICAgKiBAcGFyYW0gIHtGdW5jdGlvbn0gICAgICAgb3BzLmNvbXBpbGVyICAgIENoYW5nZSB0aGUgd2F5IGFuIGluc3RhbmNlIGJ1aWxkcyBKU09OIG9iamVjdCB7QGxpbmsgY29tcGlsZXJ9XG4gICAgICogQHJldHVybiB7SlNPTmlmaWVyfVxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHN0YXRlLCBvcHMpIHtcbiAgICAgICAgdGhpcy5vcHRpb25zID0ge1xuICAgICAgICAgICAgbmFtZXNwYWNlOiB1bmRlZmluZWQsXG4gICAgICAgICAgICBsaW1pdDogLTEsXG4gICAgICAgICAgICBjb21waWxlcjogY29tcGlsZXJcbiAgICAgICAgfTtcblxuICAgICAgICBpZiAoc3RhdGUgaW5zdGFuY2VvZiBKU09OaWZpZXIgJiYgXy5pc09iamVjdChvcHMpKSB7XG4gICAgICAgICAgICBfLmV4dGVuZCh0aGlzLm9wdGlvbnMsIG9wcyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoXy5pc09iamVjdChzdGF0ZSkgJiYgXy5pc1VuZGVmaW5lZChvcHMpKSB7XG4gICAgICAgICAgICBfLmV4dGVuZCh0aGlzLm9wdGlvbnMsIHN0YXRlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX3N0YXRlID0gc3RhdGUgaW5zdGFuY2VvZiBKU09OaWZpZXJcbiAgICAgICAgICAgID8gXy5jbG9uZURlZXAoc3RhdGUuX3N0YXRlKVxuICAgICAgICAgICAgOiB7fVxuICAgICAgICAgICAgO1xuXG4gICAgICAgIHRoaXMuX2N1cnJlbnQgPSBfLmlzVW5kZWZpbmVkKHRoaXMub3B0aW9ucy5uYW1lc3BhY2UpXG4gICAgICAgICAgICA/IHRoaXMuX3N0YXRlXG4gICAgICAgICAgICA6IGNyZWF0ZU9iamVjdCh0aGlzLl9zdGF0ZSwgdGhpcy5vcHRpb25zLm5hbWVzcGFjZSwge30pXG4gICAgICAgICAgICA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQWRkcyBhbiBlbmhhbmNlZCAnSlNPTiBvYmplY3QnIHRvIHRoZSBjdXJyZW50IGluc3RhbmNlXG4gICAgICpcbiAgICAgKiBAbWV0aG9kIGFkZFxuICAgICAqIEBwYXJhbSAge1N0cmluZ30gICAgICAgICAgICAgICAgICAgICBtZXRob2QgICAgW29wdGlvbmFsXSBEb3Qgbm90YXRpb246IGUuZy4gJ2EnIG9yICdhLmIuYy5kJ1xuICAgICAqIEBwYXJhbSAge09iamVjdHxGdW5jdGlvbnxHZW5lcmF0b3J9ICBZaWVsZCBzdGF0aWMgZGF0YSAob2JqZWN0LCBzdHJpbmcsIG51bWJlciwgZXRjLi4uKVxuICAgICAqL1xuICAgIGFkZChtZXRob2QsIGdlbmVyYXRvcikge1xuICAgICAgICBpZiAoXy5pc1N0cmluZyhtZXRob2QpKSB7XG4gICAgICAgICAgICBjcmVhdGVPYmplY3QodGhpcy5fY3VycmVudCwgbWV0aG9kLCBfLmFzc2lnbldpdGgoZ2VuZXJhdG9yLCBnZW5lcmF0b3IsIGR5bmFtaWNDdXN0b21pc2VyKSk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChfLmlzVW5kZWZpbmVkKGdlbmVyYXRvcikgJiYgXy5pc09iamVjdChtZXRob2QpKSB7XG4gICAgICAgICAgICBfLmFzc2lnbldpdGgodGhpcy5fY3VycmVudCwgbWV0aG9kLCBkeW5hbWljQ3VzdG9taXNlcik7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHRocm93IEVycm9yKCdJbGxlZ2FsIHVzZSBvZiBqc29uaWZpZXIjYWRkJyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogWWllbGRzIHN0YXRpYyBKU09OIG9iamVjdHMgZnJvbSBhbGwgaW5oZXJpdGVkIGluc3RhbmNlc1xuICAgICAqIEBtZXRob2QgYnVpbGRcbiAgICAgKiBAcmV0dXJuIHtHZW5lcmF0b3J9ICB3aGljaCB5aWVsZHMgSlNPTiBvYmplY3RzXG4gICAgICovXG4gICAgYnVpbGQoKSB7XG4gICAgICAgIGxldCB0aGF0ID0gdGhpcztcbiAgICAgICAgcmV0dXJuIGZ1bmN0aW9uKiBpdGVyYWJsZUpTT05pZmllcigpIHtcbiAgICAgICAgICAgIGZvcihsZXQgaT0wOyBpICE9IHRoYXQub3B0aW9ucy5saW1pdDsgaSA9IChpICsgMSkgJSBOdW1iZXIuTUFYX1NBRkVfSU5URUdFUikge1xuICAgICAgICAgICAgICAgIHlpZWxkIHRoYXQub3B0aW9ucy5jb21waWxlcih0aGF0Ll9zdGF0ZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0oKTtcbiAgICB9XG59O1xuIl0sInNvdXJjZVJvb3QiOiIvc291cmNlLyJ9 diff --git a/index.js b/index.js index cb0f03f..11eb654 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,17 @@ 'use strict'; const _ = require('lodash'); +/** + * Converts object to only contain static data + * + * Our state data either has static leaf values or functions which yield static + * values. This converst the entire object to only contain static data (subject + * to users creating functions which _only_ yield static output) + * + * @method compiler + * @param {Object} obj Typically jsonifier state object + * @return {Object} + */ function compiler(obj) { if (_.isFunction(obj)) { return obj(); @@ -12,6 +23,12 @@ function compiler(obj) { return obj; } + +/** + * Converts generators to functions which yield next value + * + * See [_.assignWith](https://lodash.com/docs#assignInWith) + */ function dynamicCustomiser(objValue, srcValue) { if (_.isFunction(srcValue)) { let result = srcValue(); @@ -22,8 +39,53 @@ function dynamicCustomiser(objValue, srcValue) { return srcValue; } + +/** + * Extends an object to make sure it has child attributes + * + * @method createObject + * @param {Object} srcObj The target object + * @param {String} ref Period separated description: + * a.b.c => {a: {b: {c: {}}}} + * @param {Object} finalValue [optional] final value to assign to leaf node: + * a.b.c (final 'foo') => {a: {b: {c: 'foo'}}} + * @return {Object Ref} The final objects 'a.b.c' => {c: {}} + */ +function createObject(srcObj, ref, finalValue) { + let steps = ref.split('.'); + + steps.forEach((attrib, index) => { + srcObj[attrib] = srcObj.hasOwnProperty(attrib) && _.isObject(srcObj[attrib]) + ? srcObj[attrib] + : {} + ; + if (index < steps.length - 1) { + srcObj = srcObj[attrib]; + } else { + srcObj = srcObj[attrib] = finalValue; + } + }); + + return srcObj; +} + + +/** + * Captures object of JSON data in particular states + * @class {JSONifier} + */ module.exports = class JSONifier { + /** + * @method constructor + * @param {JSONifier} state A JSONifier instance to inherit properties from + * + * @param {Object} ops Optional parameters + * @param {String} ops.namespace Period separated namespace: 'a.b.c' => {'a': {'b': 'c':{}}} + * @param {Integer} ops.limit Limit the number of responses from this instance [default: unlimited] + * @param {Function} ops.compiler Change the way an instance builds JSON object {@link compiler} + * @return {JSONifier} + */ constructor(state, ops) { this.options = { namespace: undefined, @@ -39,34 +101,27 @@ module.exports = class JSONifier { _.extend(this.options, state); } - if (state instanceof JSONifier) { - this._state = _.cloneDeep(state._state); - } else { - this._state = {}; - } + this._state = state instanceof JSONifier + ? _.cloneDeep(state._state) + : {} + ; this._current = _.isUndefined(this.options.namespace) ? this._state - : this._state[this.options.namespace] = this._state[this.options.namespace] || {} + : createObject(this._state, this.options.namespace, {}) ; } + /** + * Adds an enhanced 'JSON object' to the current instance + * + * @method add + * @param {String} method [optional] Dot notation: e.g. 'a' or 'a.b.c.d' + * @param {Object|Function|Generator} Yield static data (object, string, number, etc...) + */ add(method, generator) { if (_.isString(method)) { - let obj = this._current; - - let steps = method.split('.'); - const final_step = steps.pop(); - - steps.forEach(attrib => { - obj[attrib] = obj.hasOwnProperty(attrib) && _.isObject(obj[attrib]) - ? obj[attrib] - : {} - ; - obj = obj[attrib]; - }); - - obj[final_step] = _.assignWith(generator, generator, dynamicCustomiser); + createObject(this._current, method, _.assignWith(generator, generator, dynamicCustomiser)); return this; } @@ -78,6 +133,11 @@ module.exports = class JSONifier { throw Error('Illegal use of jsonifier#add'); } + /** + * Yields static JSON objects from all inherited instances + * @method build + * @return {Generator} which yields JSON objects + */ build() { let that = this; return function* iterableJSONifier() { diff --git a/test/basic.spec.js b/test/basic.spec.js index fe62af9..75a2892 100644 --- a/test/basic.spec.js +++ b/test/basic.spec.js @@ -6,9 +6,9 @@ function testObj(js, obj) { return js.build().next().value.should.containDeep(obj); } -describe('simple use cases', function() { +describe('varying constructor types', function() { - it('handles simple objects', () => { + it('the simplest case', () => { let simple = new jsonifier().add({ 1: 2 }); @@ -20,11 +20,19 @@ describe('simple use cases', function() { inherit.add({2:3}); testObj(inherit, {1:2, 2:3}); + }); + + it('namespaces, functions & composite', () => { + let simple = new jsonifier().add({ 1: 2 }); + + // Namespaces let dual = new jsonifier(simple, {namespace: 'b'}).add({c: 'd'}); testObj(dual, {1:2, 'b': {c: 'd'}}); + // Functions testObj(new jsonifier().add('a', () => 'success'), {'a': 'success'}); + // Composites testObj(new jsonifier().add('a', { 'b': [ {'c': () => 'd'}, @@ -40,13 +48,30 @@ describe('simple use cases', function() { }).should.throw(/Illegal/); }); - it('handles deep object generation', () => { - let simple = new jsonifier().add('a.b.c', {e: 'f'}); +}); +describe('object creation shortcut notation', function() { + + it('adds generated objects from notation', () => { + let simple = new jsonifier().add('a.b.c', {e: 'f'}); testObj(simple, {'a': {'b': {'c': {'e': 'f'}}}}); }); - it('handles dynamic content', () => { + it('handles namespaces with notation', () => { + // Simple objec5 + let ns = new jsonifier({namespace: 'a.b.c'}).add({'d': 'e'}); + testObj(ns, {'a': {'b': {'c': {'d': 'e'}}}}); + + // Function which would have to be 'compiled' + ns = new jsonifier({namespace: 'a.b.c'}).add('d', () => 'e'); + testObj(ns, {'a': {'b': {'c': {'d': 'e'}}}}); + }); + +}); + +describe('dynamic content', function() { + + it('handling function and generators', () => { // Functions let func = new jsonifier().add({ test: () => 'success' @@ -66,7 +91,11 @@ describe('simple use cases', function() { test.next().value.should.containDeep({'test': 'c'}); test.next().value.should.containDeep({'test': undefined}); - // Other + }); + + it('handles composites of functions, gernerators and static values', () => { + let func = new jsonifier().add({ test: () => 'success' }); + let mega = new jsonifier(func).add('foo.bar', { a: 'woot', bob: function* bob() { @@ -103,14 +132,18 @@ describe('simple use cases', function() { }); }); - it('enforces inheritance order', () => { +}); + +describe('other more subtle stuff', function() { + + it('enforces inheritance order overwriting', () => { let a = new jsonifier().add('a', {b: {c: 'Should not'}, d: 'woot'}); let b = new jsonifier(a).add('a.b', {c: 'make it through'}); let c = new jsonifier(b).add('a.b', {c: 'success'}); testObj(c, {'a': {'b': {'c': 'success'}, 'd': 'woot'}}); }); - it('has functional namespaces', () => { + it('has working namespaces', () => { // Normal namespace use let a = new jsonifier({namespace: 'woot'}).add({'test': 'success'}); testObj(a, {'woot': {'test': 'success'}}); @@ -118,6 +151,14 @@ describe('simple use cases', function() { // Inheritange let b = new jsonifier(a).add('test', {a: 1}); testObj(b, {'woot': {'test': 'success'}, 'test': {a: 1}}); + + // Notation Expansion + let d = new jsonifier(a, {namespace: 'woot.bob.foo'}).add({1:2}); + testObj(d, { 'woot': { 'test': 'success', 'bob': { 'foo': { '1': 2 } } } }); + + // Quirks: undefined namespace as a way to jump to a root attribute if you have to reference namespace + let c = new jsonifier({namespace: undefined}).add({'a': 'b'}); + testObj(c, {'a': 'b'}); }); });