Home

Awesome

  __                               
_/  |_____________    ____   ______
\   __\_  __ \__  \  /    \ /  ___/
 |  |  |  | \// __ \|   |  \\___ \ 
 |__|  |__|  (____  /___|  /____  >
                  \/     \/     \/ 

Latest Stable Version NPM Downloads Build Status

The ultimate object transformer

Install

$ npm install trans

Purpose

The purpose of trans is to make it super easy to transform complex json objects

Overview

Trans allows specifying composite field names such as a.b.c and it does the right thing even across multiple arrays.
For example, the field above could be used to modify or extract a value from an object that looks like this

{ a: { b: { c: 1 } } }

but also if the object looks like this

{ a: [ { b: { c: 1 } }, { b: { c: 2 } } ] }

or like this

[ { a: { b: [ { c: 1 }, { c: 2 } ] } } ]

There are three types of transformation methods:

The transformers specified as parameters to the transformation methods can be functions, field names, or even objects (which will be used as hash maps). The functions that are not properties on the object being transformed are assumed to take that object as the first parameter. But, they can take additional parameters as well. In those case the function should be specified as an array. When multiple transformers are specified the result of each one is piped over to the next one.

Here are a couple of examples which result in an identical outcome:

trans({ a: [ 1, 2 ] }).mapf('a', 'length', [add, 5], [mul, 10], square);
trans({ a: [ 1, 2 ] }).mapf('a', function(obj) {
    return square(mul(add(obj.length, 5), 10));
});

The result in both cases is:

{ a: 4900 }

Quickstart

Using trans is easy, first wrap the data to be transformed by calling trans(data),
as below, and then call transformation methods on the wrapper. Multiple transformation
methods can be chained. When done call value() to get back the raw data that has been transformed.

Here's a quick taste. Assuming we have an object that looks like this

var data = [ 
      { a: { b: 'fbc' }, c: 1 }
    , { a: { b: 'foo' }, c: 3 }
    , { a: { b: 'fde' }, c: 2 }
    , { a: { b: 'def' }, c: 3 }
    , { a: { b: 'ghk' }, c: 4 } ];

We can use trans to group the data array by the first letter capitalized of the a.b field
and set the group value to c, then sort the value array, and finally sort the entire result array by the group key as follows

var trans = require('trans');
var result = trans(data)
    .group('a.b', 'c', ['charAt', 0], 'toUpperCase')
    .sortf('value')
    .sort('key')
    .value();

After running the above code result will have the following value

[ { key: 'D', value: [ 3 ] }, { key: 'F', value: [ 1, 2, 3 ] }, { key: 'G', value: [ 4 ] } ]
<a name="methodsindex" /> ## Methods (index)

Methods (detail)

var trans = require('trans');

trans(data)

This function creates a transformation wrapper around the specified data. Further transformation methods can be chained on the trans wrapper.

value()

Unwraps the raw data object.

get(callback)

Makes the current raw data available for inspection. It can be used to insert console log statements in the transformation chain for debugging purposes.

var value = trans(data)
    .group('a.b')
    .get(console.log)
    .sort('key')
    .value();
<a name="mapfn"/> ### map(*transformers)

This is the main transformation method. It passes the entire raw object to the transformers and it replaces it with the result returned by the last transformer function.

Here is a simple example:

trans('2.32').map(parseFloat, square, [ add, 10 ]).value();

=> 15.3824

Field names, and functions that exist on the object being transformed can be specified as transformers

trans('transform me').map('toUpperCase', [ 'substring', 0, 5 ]).value();

=> 'TRANS'

trans({ a: 1 }).map('a').value();

=> 1

trans({ a: 'foo' }).map('a', 'toUpperCase', [ 'charAt', 1 ]).value();

=> 'O'

If the current object is an array the whole array is passed to the transformer functions. To transform its elements instead precede the transformers with a dot '.' which will indicate that array iteration is desired.

Here are a few array examples:

trans([ 1, 2, 3 ]).map('length').value();

=> 3

trans([ 1, 2, 3 ]).map('.', square).value();

=> [ 1, 4, 9 ]

trans([ [ 1, 2 ], [ 3 ], [ 4, 5, 6 ] ]).map('.', sum).value();

=> [ 3, 3, 15 ]

trans([ [ 1, 2 ], [ 3 ], [ 4, 5, 6 ] ]).map('.', '.', [ add, 5 ]).value();

=> [ [ 6, 7 ], [ 8 ], [ 9, 10, 11 ] ]

trans([ { a: [ 1, 2 ] }, { a: [ 3 ] }, { a: [ 4, 5, 6 ] } ])
    .map('.', 'a', 'length')
    .value();

=> [ 2, 1, 3 ]

trans([ { a: [ 1, 2 ] }, { a: [ 3 ] }, { a: [ 4, 5, 6 ] } ])
    .map('.', 'a', '.', square)
    .value();

=> [ [ 1, 4 ], [ 9 ], [ 16, 25, 36 ] ]

Objects can be specified as transformers as well. When that is the case the result of the previous transformation will be used as an index into the transformer object.

var intToName = { 1: 'one', 2: 'two', 3: 'three' };

trans([ 1, 2 ]).map('length', intToName).value();

=> 'two'

Back to Index

<a name="mapffn"/> ### mapf(field, *transformers)

This is exactly like map but it is applied at a specified field. In fact if a null field is specified the result is identical to calling map. Otherwise, the input to the first transformer function will be the value at the specified field and the result of the last transformer will replace the value at that field.

trans(1).mapf(null, [ add, 1 ]).value();

=> 2

trans({ a: 1 }).mapf('a', [ add, 1 ]).value();

=> { a: 2 }

Field names can contain dots to reach within nested objects.

trans({ a: { b: 1 } }).mapf('a.b', [ add, 1 ]).value();

=> { a: { b: 2 } }

Such field names work across arrays as well.

trans({ a: [ { b: 1 }, { b: 2 } ] }).mapf('a.b', [ add, 1 ]).value();

=> { a: [ { b: 2 }, { b: 3 } ] }

If the value at the field is an array the entire array is passed to the transformer functions.

trans({ a: { b: [ 1, 2 ] } }).mapf('a.b', 'length').value();

=> { a: { b: 2 } }

Append one last dot to the end of the field name to indicate that array iteration is desired. In such a case each array item is passed to the transformer functions and a new array is created with the results of the transformations.

trans({ a: { b: [ 1, 2 ] } }).mapf('a.b.', [ add, 1 ]).value();

=> { a: { b: [ 2, 3 ] } }

Specifying a dot '.' as the first transformer accomplishes the same thing.

trans({ a: { b: [ 1, 2 ] } }).mapf('a.b', '.', [ add, 1 ]).value();

=> { a: { b: [ 2, 3 ] } }

Back to Index

<a name="mapfffn"/> ### mapff(source, destination, *transformers)

This transformation maps the value of a field and sets the result onto another field. If the destination is null, the entire object is replaced. If the source and destination are both null it has the exact same outcome as map. If the destination field does not exist it is created, otherwise its value is replaced by the result of the transformation. The source field is left unchanged.

trans({ a: 1 }).mapff('a', 'b').value();

=> { a: 1, b: 1 }

trans({ a: 1 }).mapff('a', 'b', [ add, 1 ], square).value();

=> { a: 1, b: 4 }

Composite fields are allowed but the value passed to transformers is scoped based on where the source and destination fields point to. This becomes relevant when we are transforming across arrays.

Below the function sum gets an array containing the values of a.b, in this case [ 1, 2 ] and it computes their sum.

trans({ a: [ { b: 1 }, { b: 2 } ] }).mapff('a.b', 'c', sum).value();

=> { a: [ { b: 1 }, { b: 2 } ], c: 3 }

In the next example the scope is reduced to each object inside the array, so the transformers only get the value of the b field.

trans({ a: [ { b: 1 }, { b: 2 } ] }).mapff('a.b', 'a.c', [ add, 1 ]).value();

=> { a: [ { b: 1, c: 2 }, { b: 2, c: 3 } ] }

Same thing below, the scope is each item in the array due to the destination field pointing to items in the array.

trans({ a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] })
    .mapff('a', 'a.d', function(a) { return a.b + a.c; })
    .value();

=> { a: [ { b: 1, c: 3, d: 4 }, { b: 2, c: 3, d: 5 } ] }

Constrast the above with the next example where the destination is a field on the outer object. The scope now is the entire array that a points to.

trans({ a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] })
    .mapff('a', 'd', '.', 'b')
    .value();

=> { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ], d: [ 1, 2 ] }

If the source field points to an array we can indicate that we want to transform the elements of the array by appending one last dot to it. Alternatively, a dot '.' could be specified as the first transformer.

trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b', 'a.c', 'length').value();

=> { a: { b: [ 1, 2, 3 ], c: 3 } }

trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b.', 'a.c', [ add, 5 ]).value();

=> { a: { b: [ 1, 2, 3 ], c: [ 6, 7, 8 ] } }

trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b', 'a.c', '.', [ add, 5 ]).value();

=> { a: { b: [ 1, 2, 3 ], c: [ 6, 7, 8 ] } }

If the destination is null the entire object is replaced. This could be useful for picking up values, although, there is a pluck method for this purpose.

trans([ { a: [ { b: 1 }, { b: 2 } ] }, { a: [ { b: 3 } ] } ])
  .mapff('a.b', null)
  .value();

=> [ [ 1, 2 ], [ 3 ] ]

See the unit tests for additional examples.

Back to Index

<a name="groupfn"/> ### group(groupField, valueField, *key-transformers)

Maps an array of objects into an array of key-value pairs where the key is the value of the specified group field (possibly transformed) and the value is an array of values as indicated by the value field. If the value field is null the entire array item is used.

trans([ 'ray', 'rich', 'charles' ]).group(null, null, [ 'charAt', 0 ]).value();

=> [ { key: 'r', value: [ 'ray', 'rich' ] }, { key: 'c', value: [ 'charles' ] } ]

trans([ { a: 'ray', b: 1 }, { a: 'rich', b: 2 }, { a: 'charles', b: 3 } ])
    .group('a', 'b', [ 'charAt', 0 ], 'toUpperCase')
    .value();

=> [ { key: 'R', value: [ 1, 2 ] }, { key: 'C', value: [ 3 ] } ]

The default key and value names in the output array can be overriden with different names specified as part of the group field. The syntax of the group field is field:keyName:valueName.

trans([ 1, 1, 2, 1 ]).group(':k:v', null).value();

=> [ { k: 1, v: [ 1, 1, 1 ] }, { k: 2, v: [ 2 ] } ]

trans([ { a: 1, b: 'x' }, { a: 1, b: 'y' }, { a: 2, b: 'z' } ])
    .group('a:number:letters', 'b')
    .value();

=> [ { number: 1, letters: [ 'x', 'y' ] }, { number: 2, letters: [ 'z' ] } ]

The group field name can contain dots to reach within nested objects or arrays.

trans([ { a: [ { b: 1 }, { b: 2 } ], c: 'three' }, { a: [ { b: 10 } ], c: 'ten' } ])
    .group('a.b', 'c')
    .value();

=> [ { key: [ 1, 2 ], value: [ 'three' ] }, { key: [ 10 ], value: [ 'ten' ] } ]

trans([ { a: [ { b: 1 }, { b: 2 } ], c: 'three' }, { a: [ { b: 10 } ], c: 'ten' } ])
    .group('a.b', 'c', sum)
    .value();

=> [ { key: 3, value: [ 'three' ] }, { key: 10, value: [ 'ten' ] } ]

trans([ { a: { b: 1, c: 'one' } }, { a: { b: 11, c: 'eleven' } }, { a: { b: 2, c: 'two' } } ])
    .group('a.b', 'a.c', [ mod, 10 ])
    .value();

=> [ { key: 1, value: [ 'one', 'eleven' ] }, { key: 2, value: [ 'two' ] } ]

Other versions

Back to Index

<a name="sortfn"/> ### sort(sortField, *transformers, [comparer])

Replaces the target array with a stable sorted copy based on the value at the sort field (possibly transformed). If the last argument is a function that takes exactly two arguments it will be used as a comparer, otherwise a default comparer will be used.

trans([ 1, 2, 1, 1, 3 ]).sort(null).value();

=> [ 1, 1, 1, 2, 3 ]

trans([ 'Ash', 'bar', 'Baz', 'baz', 'bak', 'Foo', 'ash' ]).sort(null, 'toUpperCase').value();

=> [ 'Ash', 'ash', 'bak', 'bar', 'Baz', 'baz', 'Foo' ]

var intToName = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five' };

trans([ 1, 2, 3, 4, 5 ]).sort(null, intToName).value();

=> [ 5, 4, 1, 3, 2 ]

trans([ { a: 1 }, { a: 3 }, { a: 2 } ]).sort('a').value();

=> [ { a: 1 }, { a: 2 }, { a: 3 } ]

trans([ 
        { a: 1,  b: { c: 'one' } }
      , { a: 3,  b: { c: 'three' } }
      , { a: 10, b: { c: 'ten' } }
      , { a: 2,  b: { c: 'two' } } ])
    .sort('b.c')
    .value();

=>

    [ { a: 1,  b: { c: 'one' } },
      { a: 10, b: { c: 'ten' } },
      { a: 3,  b: { c: 'three' } },
      { a: 2,  b: { c: 'two' } } ]

The sort direction can be specified on the sort field. The sort field syntax is name:direction

trans([ 1, 1, 3, 2 ]).sort(':descending').value();

=> [ 3, 2, 1, 1 ]

trans([ { a: { b: 1 } }, { a: { b: 1 } }, { a: { b: 3 } }, { a: { b: 2 } } ])
    .sort('a.b:descending')
    .value();

=> [ { a: { b: 3 } }, { a: { b: 2 } }, { a: { b: 1 } }, { a: { b: 1 } } ]

See the unit tests for additional examples.

Other versions

Back to Index

<a name="objectfn"/> ### object(keyField, valueField, *key-transformers)

Transforms an array into an object where the key is the value at the specified key field (possibly transformed) and the value is as indicated by the value field. If the value field is null, the entire array item is used as the value. If multiple values map to the same key, the last one wins.

trans(['abc', 'def', 'ghk']).object(null, null, [ 'charAt', 0 ]).value();

=> { a: 'abc', d: 'def', g: 'ghk' }

trans([ { a: 'abc', b: 1 }, { a: 'def', b: 2 }, { a: 'ghk', b: 3 } ])
    .object('a', 'b', [ 'charAt', 1 ], 'toUpperCase')
    .value();

=> { B: 1, E: 2, H: 3 }

trans([ { a: 'abc', b: 1 }, { a: 'def', b: 2 }, { a: 'ghk', b: 3 } ])
    .object('a', null, [ 'charAt', 1 ], 'toUpperCase')
    .value();

=> { B: { a: 'abc', b: 1 }, E: { a: 'def', b: 2 }, H: { a: 'ghk', b: 3 } }

See the unit tests for additional examples.

Other versions

Back to Index

<a name="arrayfn"/> ### array(keyName, valueName)

Transforms an object into an array where each item is a key-value pair containing each key and its value. The key and value names can be specified, otherwise they will default to key and value.

trans({ a: 1, b: 2, c: 3 }).array().value();

=> [ { key: 'a', value: 1 }, { key: 'b', value: 2 }, { key: 'c', value: 3 } ]

trans({ a: 1, b: 2, c: 3 }).array('letter', 'number').value();

=> [ { letter: 'a', number: 1 }, { letter: 'b', number: 2 }, { letter: 'c', number: 3 } ]

If the target object is an array, array will be applied to every item in the array.

trans([ { a: 1, b: 2 }, { a: 3, b: 4, c: 5 }, { d: 6 } ]).array('k', 'v').value();

=>

[ [ { k: 'a', v: 1 }, { k: 'b', v: 2 } ],
  [ { k: 'a', v: 3 }, { k: 'b', v: 4 }, { k: 'c', v: 5 } ],
  [ { k: 'd', v: 6 } ] ]

Other versions

Back to Index

<a name="filterfn"/> ### filter(filterField, *transformers)

Filters an array by the value at the specified field (possibly transformed). If the result of the last transformer is not a boolean value, it will be converted to one before filtering.

trans([1, 2, 1, 4, 5]).filter(null, [ mod, 2 ]).value();

=> [ 1, 1, 5 ]

trans([ { a: { b: 1 } }, { a: { b: 0 } }, { a: { b: 3 } } ]).filter('a.b').value();

=> [ { a: { b: 1 } }, { a: { b: 3 } } ]

trans([ { a: 'real' }, { a: 'rock' }, { a: 'star' } ])
    .filter('a', function (s) { return s.charAt(0) === 'r'; })
    .value();

=> [ { a: 'real' }, { a: 'rock' } ]

The filtering can be inverted by indicating invert on the filter field.

trans([1, 2, 1, 4, 5]).filter(':invert', [ mod, 2 ]).value();

=> [ 2, 4 ]

trans([ { a: 'real' }, { a: 'rock' }, { a: 'star' } ])
    .filter('a:invert', [ 'charAt', 0 ], [ 'localeCompare', 'r' ])
    .value();

=> [ { a: 'real' }, { a: 'rock' } ]

Other versions

Back to Index

<a name="flattenfn" /> ### flatten(deep)

Flattens nested arrays. If deep is set to false or not specified only the first level is flattened.

trans([ [ 1 ], [ 2 ], [ [ 3, 4 ], 5 ], [ [ 6 ] ] ]).flatten().value();

=> [ 1, 2, [ 3, 4 ], 5, [ 6 ] ]

trans([ [ 1 ], [ 2 ], [ [ 3, 4 ], 5 ], [ [ 6 ] ] ]).flatten(true).value();

=> [ 1, 2, 3, 4, 5, 6 ]

Other versions

Back to Index

<a name="defaultfn" /> ### default(key1, value1, key2, value2, ...)

Fills in undefined and null values with the specified defaults. The number of arguments is expected to be an even number.

trans({ a: { b: null, c: { }, d: { e: { g: 4 } } } })
    .default('a.b', 1, 'a.c.f', 2, 'a.d.e.f', 5)
    .value();

=> { a: { b: 1, c: { f: 2 }, d: { e: { f: 5, g: 4 } } } }

If the target object is an array, default is applied to every item in the array.

trans([{}, { a: 1 }, { a: null }, { a: 4 }, {} ]).default('a', 100).value();

=> [ { a: 100 }, { a: 1 }, { a: 100 }, { a: 4 }, { a: 100 } ]

trans([ { a: [ { b: 1 }, {} ] }, { a: [ {} ] } ]).default('a.b', 10).value();

=> [ { a: [ { b: 1 }, { b: 10 } ] }, { a: [ { b: 10 } ] } ]

Back to Index

<a name="pickfn" /> ### pick(*fields)

Creates new objects that contain only the specified fields.

trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).pick('a.b', 'e').value();

=> { a: { b: 1 }, e: 6 }

If the target object is an array, pick is applied to every item in the array.

trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).pick('a.b').value();

=> { a: [ { b: 1 }, { b: 3 } ] }

Other versions

Back to Index

<a name="omitfn" /> ### omit(*fields)

Creates new objects that do not contain the specified fields. Calling omit with no parameters will create a deep copy of the current object.

trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).omit('a.c', 'd').value();

=> { a: { b: 1 }, e: 6 }

If the target object is an array, omit is applied to every item in the array.

trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).omit('a.c', 'd').value();

=> { a: [ { b: 1 }, { b: 3 } ] }

Other versions

Back to Index

<a name="removefn" /> ### remove(*fields)

Traverses the object tree and deletes the specified fields in place.

trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).remove('a.c', 'd').value();

=> { a: { b: 1 }, e: 6 }

If the target object is an array, remove is applied to every item in the array.

trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).remove('a.c', 'd').value();

=> { a: [ { b: 1 }, { b: 3 } ] }

Back to Index

<a name="pluckfn" /> ### pluck(pluckField, *transformers)

Extracts the value(s) at the indicated field.

trans({ a: { b: 100 } }).pluck('a.b').value();

=> 100

trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b').value();

=> [ 1, 2, 3 ]

trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b', sum).value();

=> 6

trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b', '.', [ add, 1 ]).value();

=> [ 2, 3, 4 ]

trans([ { a: { b: [ { c: 1 } ] } }, { a: { b: [ { c: 3 }, { c: 4 } ] } } ])
    .pluck('a.b.c')
    .value();

=> [ [ 1 ], [ 3, 4 ] ]

Other versions

Back to Index

<a name="skipfn" /> ### skip(count)

Creates a new array that skips the specified number of items.

trans([ 1, 2, 3, 4, 5, 6 ]).skip(2).value();

=> [ 3, 4, 5, 6 ]

Other versions

Back to Index

<a name="takefn" /> ### take(count)

Creates a new array that contains only the first specified number of items.

trans([ 1, 2, 3, 4, 5, 6 ]).take(2).value();

=> [ 1, 2 ]

Other versions

Back to Index

<a name="firstfn" /> ### first()

Replaces the target array with its first element.

trans([ 1, 2, 4 ]).first().value();

=>1

trans([]).first().value();

=> null

Other versions

Back to Index

<a name="lastfn" /> ### last()

Replaces the target array with its last element.

trans([ 1, 2, 4 ]).last().value();

=>4

Other versions

Back to Index

<a name="uniqfn" /> ### uniq(uniqField, *transformers)

Removes duplicate items from an array according to the value at the specified field (possibly transformed).

trans([ 1, 1, 2 ]).uniq().value();

=> [ 1, 2 ]

trans([ 1, 11, 12, 22 ]).uniq(null, [ mod, 10 ]).value();

=> [ 1, 12 ]

trans([ { a: 'abc' }, { a: 'aab' }, { a: 'bcd' }, { a: 'bad' } ])
    .uniq('a', [ 'charAt', 0 ])
    .value();

=> [ { a: 'abc' }, { a: 'bcd' } ]

Other versions

Back to Index

Gotchas and Limitations

Some transformations will modify the original data while others won't. See the two examples below.

var a = [ 1, 2, 3 ];
var t = trans(a).map('shift').value();

console.log(a);
console.log(t);

=> [ 2, 3 ]
=> 1

var a = [ 1, 2, 3 ];
var t = trans(a).map(['slice', 0, 1], 'shift').value();

console.log(a);
console.log(t);

=> [ 1, 2, 3 ]
=> 1

Calling mapff without any transformer functions will just create another reference to the same object. This may lead to unexpected results.

var obj = { a: { b: 2, c: 'X' } };
var res = trans(obj).mapff('a', 'c').value();

console.log(res);

res.a.c = 'changed';
console.log(res);

=> { a: { b: 2, c: 'X' }, c: { b: 2, c: 'X' } }
=> { a: { b: 2, c: 'changed' }, c: { b: 2, c: 'changed' } }

Calling mapff without any transformer functions and setting the destination to be a field on the source object would create a circular reference.

trans({ a: { b: { c: 1 } } }).mapff('a', 'a.b.d').value();

=> { a: { b: { c: 1, d: [Circular] } } }

License

MIT