This package will help facilitate data retrieval, mutation, and transformation. It's aim is to:
- stay small
- alleviate the need for
null
checking - easily allow pipe/compose/chaining of data flows
- allow for a default value to be returned instead of
null
orundefined
at any point within the chain - alleviate the need to create empty data structures when trying to set a nested value
Two main function exists: get()
and set()
along with their curried version _get()
and _set()
. All other exported functions are helpers and are meant to facilitate their use.
One major difference to note between this implementation and that of Partial-Lenses is that the values returned from get()
are not clones. They are the actual pointer references. Modifying these returned values are the equivalent to directly modifying the object you retrieved the values from. In general, get()
should not be used for Object mutation. It should only be used for data retrieval. Use set()
if you wish to mutate an Object.
It should also be noted that undefined
and null
will be treated as the same. i.e. a non-existent value. get()
, _get()
and all helper functions will actively ignore inputs that are either undefined
or null
and return immediately the input value.
const { get, set, defaults } = require('@rybr/lenses')
const myInput = {
a: {
b: [null, { c: 1 }],
},
}
get(myInput, 'a', 'b', 1, 'c')
//returns 1
get(myInput, 'a', 'b', 0, 'f', defaults('someValue'))
//0 nor 'f' exist so this defaults to 'someValue'
//returns 'someValue'
get(myInput, 'a', 'f', defaults('default'), (x) => x + '!')
//'f' does not exist so this adds a default value to protect the anonymous function
//returns 'default!'
const newInput = { a: 1 }
set(newInput, 'b', 1, 1)
//returns newInput which is now { a: 1, b: [ undefined, 1 ] }
set(newInput, 'c', 'd', 1)
//will automatically create an Object for 'c'
//returns newInput which is now { a: 1, b:[ undefined, 1 ], c: { d: 1 } }
set(newInput, 'c', 'd', (oldVal) => oldVal + 1)
//Navigates to 'd' and then sends the retrieved value to the provided function.
//The returned value is then used to set the value for 'd'
//returns newInput which is now { a: 1, b: [ undefined, 1 ], c: { d: 2 } }
set(
newInput,
'c',
'e',
_get(defaults(10), (oldVal) => oldVal + 1)
)
//Navigates to 'e'
//Uses _get() to specify a default value in case 'e' is undefined
//Sends the retrieved value to the provided function.
//The returned value is then used to set the value for 'e'
//returns newInput which is now { a: 1, b: [ undefined, 1 ], c: { d: 2, e: 11 } }
get( input, operation1, operation2, opertaion3, ..., operationN )
or
_get( operation1, operation2, opertaion3, ..., operationN )(input)
Retrieves the value for the given input
based on the operations
provided.
input
: anything you would like. It will be the input to operation1
operationN
: takes the resulting value from the previous operation as input and performs the given operation on it. The result is then passed as input to the next operation in line. If it is the last operation, the result is returned as the final value. Can be one of three types: String
, Number
, or Function
-
String
: short-hand to retrieve the value for the specifiedkey
from a givenObject
-
Number
: short-hand to retrieve the value at the specifiedindex
from a givenArray
. -
Function
: this can be either a custom function or one of the helper functions. The input will always be the resulting value from the previous operation.
set( input, operation1, operation2, operation3, ..., operationN, value )
or
_set( operation1, operation2, operation3, ..., operationN, value )(input)
With the input
as the entry point, navigate the path specified by the operations
and then set the final retrieved entity equal to value
. Data structures will be created automatically during navigation if they do not exist.
input
: anything you would like. It will be the input to operation1
operationN
: takes the resulting value from the previous operation as input and performs the given operation on it. The result is then passed as input to the next operation in line. If it is the last operation, the result is returned as the final value. Can be one of two types: String
or Number
-
String
: short-hand to retrieve the value for the specifiedkey
from a givenObject
-
Number
: short-hand to retrieve the value at the specifiedindex
from a givenArray
.
value
: anything you would like. If a Function
is provided, it will be evaluated with the input being equal to whatever value was retrieved by the last operation
. This will allow you to dynamically set the value based on its current value.
By default the code is split so if you only want to import a specific section you may
npm install -D @rybr/lenses
//everything
import { get, defaults, set, func, parse, stringify, map, filter, ...rest } from '@rybr/lenses'
//explicity import
import { get, defaults } from '@rybr/lenses/get'
import { set } from '@rybr/lenses/set'
import { func, parse, stringify, ...rest } from '@rybr/lenses/funcs'
import { map, filter, slice, ...rest } from '@rybr/lenses/protos'
//production (everything)
<script src="https://unpkg.com/@rybr/lenses/dist/lenses.min.js"></script>
//development (everything)
<script src="https://unpkg.com/@rybr/lenses/lenses.js"></script>
//production (explicity import)
<script src="https://unpkg.com/@rybr/lenses/dist/get.min.js"></script>
<script src="https://unpkg.com/@rybr/lenses/dist/set.min.js"></script>
<script src="https://unpkg.com/@rybr/lenses/dist/funcs.min.js"></script>
<script src="https://unpkg.com/@rybr/lenses/dist/protos.min.js"></script>
//development (explicity import)
<script src="https://unpkg.com/@rybr/lenses/get.js"></script>
<script src="https://unpkg.com/@rybr/lenses/set.js"></script>
<script src="https://unpkg.com/@rybr/lenses/funcs.js"></script>
<script src="https://unpkg.com/@rybr/lenses/protos.js"></script>
Note: This will automatically fetch the latest version. This will load globally into window.L
Download the desired script from https://unpkg.com/@rybr/lenses/sfcc/lenses.js.
I recommend placing this script in */cartridge/scripts/util/lenses
Use require
from within the controllers like normal.
These scripts have been bundled as commonJs
modules and have been translated to base ES5 so they should work in the controllers.
const L = require('*/cartridge/scripts/util/lenses')
Generic polyfills have been included to support several commonly used Array prototype functions found in ES6 and later versions of ES5.
Support has been added for the Java derived Collection class and those that implement this interface. These functions should work transparently with the normal JS Array. E.g.
const L = require('*/cartridge/scripts/util/lenses')
L.get(
someCollection, //SFCC Collection
L.find(function (item) {
return item && item.someProp
})
)
L.get(
[1, 2, 3, 4, 5], //JS Array
L.find(function (num) {
return num > 3
})
)
These are to be used in conjunction primarly with get()
These are all curried and can be used within the pipeline of the get()
part of
funcs.js
Wraps the function unsafeFunction
so that it returns the input
if the input is null
or undefined
part of
protos.js
Attempts to use the call the function of the given input
specified by prototypeName
part of
funcs.js
console.log
the input
. Prepends the log with customInput
(if it exists). By default it will prettify the output.
part of
get.js
If input
is null
or undefined
then return defaultValue
else return input
part of
protos.js
Most prototype functions are available and will be curried by default so that they can be used easily with get
Here are some more examples with full value tracking, notes, and explanations
import { get, defaults, func } from '@rybr/lenses'
const myInput = {
a: [
null,
{
b: 5
}
]
}
////////////////////
// EXAMPLE 1 //
////////////////////
get(
myInput,
'a', //myInput.a | [null, { b: 5 }]
0, //myInput.a[0] | null
'b' //myInput.a[0].b | null
'c' //myInput.a[0].b.c | null
x => x && x * 50 //(myInput.[0].b.c) * 50 | null
) //returns null
//Note how the null value is propegated even though there are other operations after the initial null is returned.
//Note how we had to manually null check when providing a custom anonymous function.
//In this case, null *will* be passed as the input. It is up to you to null check when using a custom function
//A way to avoid having to include null checks in your custom functions is to wrap them inside the func() helper function
//You could also specify a default value before each anonymous function to guarentee a non-null input
e.g.
get(
myInput,
'a', //myInput.a | [null, { b: 5 }]
0, //myInput.a[0] | null
'b' //myInput.a[0].b | null
'c' //myInput.a[0].b.c | null
func(x => x * 50) //(myInput.[0].b.c) * 50 | null
) //returns null
//Use this when you don't want to handle null checking
////////////////////
// EXAMPLE 2 //
////////////////////
get(
myInput,
'a', //myInput.a | [null, { b: 5 }]
1, //myInput.a[1] | { b : 5 }
'b' //myInput.a[1].b | 5
) //returns 5
////////////////////
// EXAMPLE 3 //
////////////////////
get(
myInput,
'a', //myInput.a | [null, { b: 5 }]
1, //myInput.a[1] | { b : 5 }
'c', //myInput.a[1].c | undefined
defaults(25), //myInput.a[1].c || 25 | undefined || 25
input => input * 3 //(myInput.a[1].c || 25) * 3 | 25 * 3
) //returns 75
TODO
import { get, set, func, map } from '@rybr/lenses'
////////////////////
// EXAMPLE 1 //
////////////////////
const myArray = [
{a: null},
{a: 2},
{a: 3},
]
const addThree = map(input => get(input, 'a', defaults(0)) + 3)
const addProperty = map(input => set(input, 'b', 'new'))
get(
myArray,
addThree,
addProperty,
)
//returns [ {a: 4, b: 'new' }, { a: 5, b: 'new' }, { a: 6, b: 'new' }]
//Note that myArray remains unchanged. This is because we used map which
//inherently always returns a new Array without modifying the original
//Alternatlively, you can do the same thing withought declaring the functions
//which is a little easier to read since you definitely know what the input will be into each map function in this case
get(
myArray,
map(input => get(input, 'a', defaults(0)) + 3),
map(input => set(input, 'b', 'new'))
)
////////////////////
// EXAMPLE 2 //
////////////////////
const myObject = {
texts: {
labels: [
'this is a string with BAD capitals in it and illegal ** chars',
'MORE bad ** text here'
]
}
}
const toLower = func(input => input.toLowerCase())
const removeInvalidChars => func(input => input.replace('*', ''))
get(
myObject,
'texts',
'labels',
map(input => get(input, toLower, removeInvalidChars))
)
//Will return a list of modified labels however it will *NOT* modify myObject directly
//This is because `.map` never modifies the original Array