UNPKG

map-transform

Version:

Map and transform objects with mapping definitions

294 lines 10.1 kB
import mapAny from 'map-any'; import modify from './modify.js'; import { getStateValue, setStateValue, getTargetFromState, setTargetOnState, getLastContext, getRootFromState, isNonvalue, revFromState, clearUntouched, } from '../utils/stateHelpers.js'; import { isObject } from '../utils/is.js'; import { ensureArray, indexOfIfArray } from '../utils/array.js'; const adjustIsSet = (isSet, state) => revFromState(state, isSet); function flatMapAny(fn) { return (value, target) => Array.isArray(value) ? value.flatMap((value) => fn(value, target)) : fn(value, target); } function handleArrayPath(path) { if (path.endsWith('][')) { return [path.slice(0, path.length - 2), false, true]; } const pos = path.indexOf('['); if (path[pos - 1] === '\\') { return [path.replace('\\[', '['), false, false]; } else { const isArr = path[pos + 1] === ']'; return [path.slice(0, pos), isArr, false]; } } function preparePath(path) { if (typeof path === 'string') { if (path.includes('[')) { return handleArrayPath(path); } else if (path.startsWith('\\$')) { return [path.slice(1), false, false]; } } return [path, false, false]; } function getSetProp(path) { if (path === '') { return (value) => value; } const getFn = flatMapAny((value) => isObject(value) ? value[path] : undefined); const setFn = (value, target) => isObject(target) ? { ...target, [path]: value } : { [path]: value }; return (value, isSet, target) => { if (isSet) { return setFn(value, target); } else { return getFn(value); } }; } const calculateIndex = (index, arr) => index >= 0 ? index : arr.length + index; function getSetIndex(index) { return (value, isSet, target) => { if (isSet) { const arr = Array.isArray(target) ? [...target] : []; arr[calculateIndex(index, arr)] = value; return arr; } else { return Array.isArray(value) ? value[calculateIndex(index, value)] : undefined; } }; } function getParent(state) { const nextValue = getLastContext(state); const nextContext = state.context.slice(0, -1); return { ...state, context: nextContext, value: nextValue }; } function getRoot(state) { const nextValue = getRootFromState(state); return { ...state, context: [], value: nextValue }; } function getSetParentOrRoot(path, isSet) { const getFn = path[1] === '^' ? getRoot : getParent; return () => (next) => async (state) => { const nextState = await next(state); if (adjustIsSet(isSet, state)) { return setStateValue(nextState, state.target); } else { return getFn(nextState); } }; } const modifyOnSet = (isSet) => (options) => function modifyOnSet(next) { const modifyFn = modify('.')(options)(next); return async (state) => { return adjustIsSet(isSet, state) ? await modifyFn(state) : setStateValue(await next(state), undefined); }; }; function doModifyGetValue(value, state, options) { const { modifyGetValue } = options; return typeof modifyGetValue === 'function' ? modifyGetValue(value, state, options) : value; } function getSet(isSet = false) { return (path) => { if (typeof path === 'string') { if (path === '$modify') { return modifyOnSet(isSet); } else if (path[0] === '^') { return getSetParentOrRoot(path, isSet); } } const [basePath, isArr, isIndexProp] = preparePath(path); const isIndex = typeof basePath === 'number'; const getSetFn = isIndex ? getSetIndex(basePath) : getSetProp(basePath); return (options) => (next) => async function doGetSet(state) { if (adjustIsSet(isSet, state)) { const target = getTargetFromState(state); const nextTarget = getSetFn(target, false); const nextState = await next(setTargetOnState({ ...state, iterate: state.iterate || isArr }, nextTarget)); const setIt = (value, index) => getSetFn(value, true, indexOfIfArray(target, index)); const nextValue = getStateValue(nextState); if (state.noDefaults && isNonvalue(nextValue, options.nonvalues)) { return setStateValue(state, target); } const value = isArr ? ensureArray(nextValue, options.nonvalues) : nextValue; const thisValue = nextState.iterate && !isArr && !isIndexProp ? mapAny(setIt, value) : setIt(value); return setStateValue(state, thisValue); } else { const nextState = await next(state); const thisValue = getSetFn(getStateValue(nextState), false); const modifiedValue = doModifyGetValue(thisValue, nextState, options); const value = state.noDefaults && isNonvalue(modifiedValue, options.nonvalues) ? undefined : isArr ? ensureArray(modifiedValue, options.nonvalues) : modifiedValue; return setStateValue(nextState, value, true); } }; }; } function resolveArrayNotation(path, pos) { const index = Number.parseInt(path.slice(pos + 1), 10); if (!Number.isNaN(index)) { const basePath = path.slice(0, pos).trim(); return basePath ? [`${basePath}][`, index] : [index]; } else { return path.trim(); } } function resolveParentNotation(path) { if (path.startsWith('^^') && path.length > 2) { return ['^^', path.slice(2).trim()]; } else if (path.length > 1 && path !== '^^') { return ['^^', path.slice(1).trim()]; } else { return path.trim(); } } function splitUpArrayAndParentNotation(path) { const pos = path.indexOf('['); if (pos > -1 && path[pos - 1] !== '\\') { return resolveArrayNotation(path, pos); } else if (path.startsWith('^')) { return resolveParentNotation(path); } else { return path.trim(); } } function pathToNextOperations(path, isSet = false) { if (!path || path === '.') { return [ () => (next) => async (state) => clearUntouched(await next(state)), ]; } if (path[0] === '>') { path = path.slice(1); isSet = true; } const parts = path.split('.').flatMap(splitUpArrayAndParentNotation); const operations = parts.map(getSet(isSet)); if (isSet) { operations.reverse(); } return operations; } const getByPart = (part, isArr) => (value) => { if (typeof part === 'string') { if (isObject(value)) { const nextValue = value[part]; return isArr ? ensureArray(nextValue) : nextValue; } } else if (typeof part === 'number' && Array.isArray(value)) { return value[calculateIndex(part, value)]; } return isArr ? [] : undefined; }; function prepareGetFn([part, isArr]) { if (typeof part === 'string' && part[0] === '^') { const isRoot = part[1] === '^'; return (_value, state) => { const nextState = isRoot ? getRoot(state) : getParent(state); return [nextState.value, nextState]; }; } else if (typeof part === 'number') { const fn = getByPart(part, isArr); return (value) => [fn(value), undefined]; } else { const fn = flatMapAny(getByPart(part, isArr)); return (value) => [fn(value), undefined]; } } const setOnObject = (part) => (value) => part ? { [part]: value } : value; const setByPart = (part, isArr, doIterate) => (value) => { const data = isArr ? ensureArray(value) : value; if (typeof part === 'number') { const arr = []; const index = part < 0 ? 0 : part; arr[index] = data; return arr; } else { return doIterate ? mapAny(setOnObject(part), data) : setOnObject(part)(data); } }; function getDoIterateFromLastPart(parts) { if (parts.length === 0) { return false; } const lastPart = parts[parts.length - 1]; return lastPart[1] || lastPart[2]; } const setDoIterateOnParts = (parts, [part, isArr]) => [ ...parts, [part, isArr, isArr ? false : getDoIterateFromLastPart(parts)], ]; export function pathGetter(path, _options = {}) { if (!path || path === '.') { return (value) => value; } const parts = path .split('.') .flatMap(splitUpArrayAndParentNotation) .map(preparePath) .map(prepareGetFn); return function getFromPath(value, startState) { let data = value; let state = startState; for (const partOrGetFn of parts) { ; [data, state = state] = partOrGetFn(data, state); } return data; }; } export function pathSetter(path, options = {}) { if (typeof path !== 'string' || path === '' || path === '.') { return (value) => value; } else if (path[0] === '^') { return () => undefined; } const setFns = path .split('.') .flatMap(splitUpArrayAndParentNotation) .map(preparePath) .reduce(setDoIterateOnParts, []) .map(([part, isArr, doIterate]) => setByPart(part, isArr, doIterate)); return function setToPath(value, state) { if (state.noDefaults && isNonvalue(value, options.nonvalues)) { return undefined; } else { return setFns.reduceRight((value, setFn) => setFn(value), value); } }; } export const get = (path) => pathToNextOperations(path, false); export const set = (path) => pathToNextOperations(path, true); //# sourceMappingURL=getSet.js.map