flat2
v1.3.0
Published
Flatten a nested Javascript object any way you like. Designed to be highly customisable
Downloads
9
Maintainers
Readme
flat2
Flatten a nested Javascript object any way you like. Designed to be highly customisable
Intro
This library is a fully rewritten version of flat.
flat2
has been designed using ES6 classes to make for a more composable, flexible and more maintainable library.
It should now be much easier to test, debug and extend. It is designed to be way more customisable and flexible than the original flat
. Please also consider the alternative libs listed at the end of this Readme.
Status
All tests pass except for typed arrays
which is rarely used in any case.
I welcome a PR to fix this little issue.
Installation
$ npm install flat2 --save
Methods
flatten(obj, opts)
Creates a flattener, flattens the object and returns the result.
const { flatten } = require('flat2')
const flatObj = flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
By default uses .
as the delimiter. The key generation can be customised in many ways by passing options.
Flattener
Instead of using the convenience flatten
function, you can also option to instantiate a Flattener
instance for reuse, to flatten multiple objects.
The flattener is reset at the start of each flatten operation.
createFlattener(obj, options)
Returns a flattener
instance.
const { createFlattener } = require('flat2')
const flattener = createFlattener(obj1, opts)
Use flattener.flat(obj)
to flatten objects
// flatten using initial target
const flatObj1 = flattener.flat()
// reuse flattener and flatten with new target
const flatObj2 = flattener.flat(obj2)
You can also import the Flattener
class and use new
or extend it to create your own custom Flattener
import {
Flattener
} from 'flat2'
class MyFlattener extends Flattener {
// custom overrides/extensions
}
const flattener = new MyFlattener(obj, opts)
/// use it!
const flatObj = flattener.flatten()
Options
The following are the supported options, ie. second argument to flatten
function (or Flattener
constructor)
delimiter
Use a custom delimiter for (un)flattening your objects, instead of .
.
flatten(obj, {
delimiter: ':'
}
Transform options
toUpperCase
Use toUpperCase
option for flattening your objects and upper case object keys at the same time. This can be handy when working with constants, i.e. API_KEY: 'some key'
. This is a built in key transform.
flatten(obj, {
toUpperCase: true
}
toLowerCase
Use a toLowerCase
option for flattening your objects and lower case object keys at the same time. This is a built in key transform.
flatten(obj, {
toLowerCase: true
}
keyType
Use a built in type of key transformation function:
snakeCase
(ie.ale_beta_zeta
)camelCase
(ie.aleBetaZeta
)
flatten(obj, {
keyType: 'snakeCase'
}
Advanced Configuration options
You can use the following options to further customize how keys are generated
transformKeyFn
Use a custom function to transform object keys (as opposed to built in transforms such as toLowerCase
and toUpperCase
). A transform is applied after the key name has been generated.
flatten(obj, {
transformKeyFn: (key) => mySpecialTransform(key)
}
keyNameFn
Use a custom function
to flatten the keyname. By default, the delimiter
is inserted between prev
and next
Here's an example that uses a colon (:
) to both prefix and delimit the keyname
var obj = {
hello: {
world: {
again: 'good morning'
}}}
flatten(obj, { keyNameFn: function(prev, next) {
return prev
? prev + ':' + next // delimit
: ':' + next // prefix
}})
// {
// ':hello:world:again': 'good morning'
// }
createKeyNameFn
Use a custom factory function to create the keyname function.
const {
myDefaultKeyNameFn,
createMySpecialKeyNameFn
} = './key-functions'
flatten(obj, {
createKeyNameFn(opts) {
return opts.special ? createMySpecialKeyNameFn(opts) : myDefaultKeyNameFn
}
})
createFlatKeyFn
Use createFlatKeyFn
to pass a factory that generates a function which implements the FlatKey
interface (see FlatKeyClass
below)
const {
myFlatKey,
} = './key-functions'
flatten(obj, {
createFlatKeyFn(opts) {
return function (opts) {
return {
config(prev, next) {
// ...
}
get name() {
//
}
}
}
}
})
FlatKeyClass
Use a custom FlatKey
class. Must implement the following interface:
config(key, prev)
name
getter
It is used by the default keyNameFn
to generate each keyname as follows
return (key, prev) => {
return flatKey.config(key, prev).name
}
Internal Flow configuration
In case you want to customize the internal flow logic:
createStepper
To pass a custom createStepper
function to override the built-in createStepper
factory. You can f.ex extend the Stepper
class with your customisations.
function createStepper(object, {
flattener,
prev,
currentDepth
}) {
return new MyStepper(object, {
flattener,
prev,
currentDepth
})
}
flatten(obj, {
createStepper
})
createKeyStepper
To pass a custom createKeyStepper
function to override the built-in createKeyStepper
factory. You can f.ex extend the KeyStepper
class with your customisations.
function createKeyStepper(key, { stepper, flattener }) {
return new MyKeyStepper(key, { stepper, flattener })
}
flatten(obj, {
createKeyStepper
})
Customized output
transformValue
Pass a transformValue
function as option to fine-tune if/how leaf values are transformed. By default the value is returned, but you have access to loads of information to fine-tune if necessary.
function transformValue(value, {
target,
key,
newKey,
prevKey,
ancestorKeys,
lvKeys
}) {
return value
}
Modes
You can enable safe
mode to preserve values such as arrays.
safe
When enabled flat
will preserve arrays and their
contents. This is disabled by default.
var flatten = require('flat')
flatten({
this: [
{ contains: 'arrays' },
{ preserving: {
them: 'for you'
}}
]
}, {
safe: true
})
// {
// 'this': [
// { contains: 'arrays' },
// { preserving: {
// them: 'for you'
// }}
// ]
// }
Control flow options
maxDepth
Maximum number of nested objects to flatten.
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
}, { maxDepth: 2 })
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a': { b: { c: 2 } }
// }
filter
Decide if a value should be flattened any further
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
}
}, { filter: (value) => !value.keyA }) // skip key1
// {
// key1: {
// keyA: 'valueI'
// },
// 'key2.keyB': 'valueII'
// }
Event handlers
The Flattener
and Stepper
classes comes with built-in event handlers to maintain stacks of visited nodes. You can override these event handler to suit your own needs.
Flattener callbacks
onKey(key, depth)
onStepsDone(depth)
These events are used to build the stack of ancestorKeys
visited while traversing a branch depth first.
Stepper callbacks
onKey(key)
A stepper steps through all keys on one depth level.
The onKey
handler of stepper is used to build a stack of visited keys on a particular depth level. It also calls onKey
for the flattener to allow it to build up its stack.
Logging
You can pass a boolean logging
option to enable/disable logging.
Add a logOnly
option to limit which "classes" should log. A class is any object that has a name
property (note: name
will default to constructor.name
).
flatten({
HELLO: {
WORLD: {
AGAIN: 'good morning'
}
}
}, {
toLowerCase: true,
logging: true,
logOnly: ['Flattener']
})
Currently you can supply a logWhen
option that takes an object
which can be an instance of either:
Flattener
Stepper
KeyStepper
FlatKey
All of these instances should have access to flattener
where you have access to attributes such as:
currentDepth
lastAncestorKey
ancestorKeys
lvKeys
(via currentstepper
attached)
You can use these attributes to further control when to log.
See TODO.md
for our Logger
plans.
logger option
You can pass your own custom logger in the new logger
option.
The custom logger can implement one or more of the following:
log(...args)
objLog(...args)
The default Logger
methods:
log(...args) {
this.shouldLog && logInfo(this.name, ...args)
}
objLog(...args) {
this.shouldLog && logObj(this.name, ...args)
}
Your custom logger
can be an instance of your custom logger class that extends Logger
(also exported for convenience)
Example:
import {
Logger,
// ...
} from 'flat2`
class MyLogger extends Logger {
constructor(flattener, opts) {
super(opts)
this.flattener = flattener
}
get shouldLog() {
// custom conditions
}
}
const flattener = createFlattener(obj, opts)
const loggingOpts = {
logging: true,
// ... custom opts ?
}
const logger = new MyLogger(flattener, loggingOpts)
flattener.logger = logger
// now ready to use flattener with new logger!
TODO: Improved logger and composition
We would welcome a PR with more detailed logging constraints, such as current depth level, keys visited etc. and using composition instead of deep inheritance
Publish/Subscribe
It might sometimes be useful to have a way to post generate the output values after the fact using the information available at the time of generation for each flat key.
This can f.ex be useful for mapping values (pointers) from the nested to the flat structure.
You can now pass a subscribeValue
callback, which can be called after the fact by the flattener.
import {
leaf,
// ... more refs imported
} from 'flat2'
function subscribeValue(newKey, newValue, {
target,
ancestorKeys
})
leaf(target, ancestorKeys.join('.'), newValue)
}
The function leaf is also included and exported.
function leaf(obj, path, value) {
const pList = path.split('.');
const key = pList.pop();
const pointer = pList.reduce((accumulator, currentValue) => {
if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
return accumulator[currentValue];
}, obj);
pointer[key] = value;
return obj;
}
This way you can map the deeply nested leaf values of a target object to point to leaf values in an obj with the flat key structure.
// assuming flatStruct has structure mirroring object of returned by flatten
const styles = createStyleSheet(flatObj)
const nestedStyles = {}
flattener.publishObj(styles, target)
Now nestedStyles.card.container
will point to styles.cardContainer
and so on ;)
Note: You can also publish key/value pair individually using flattener.publish(key, value, target)
See also: Safely accessing deeply nested values
Alternatives
There are a host of alternative solutions you might want to consider...