UNPKG

jsonpath-mapper

Version:

A json to json transformation utility with a few nice features to use when translating for example API responses into a domain object for use in your domain-driven JavaScript applications. Can be used in React applications with the 'useMapper' hook.

200 lines (177 loc) 5.55 kB
import { fromEntries, isArray, isMapperFunctions, isNullOrUndefined, isNumber, isObject, isString, isUndefined, isWireFunction, jpath, } from './util.js'; import { MappingTemplate, MappingElement } from './models/Template.js'; import { FormatResult, HandleMapperFunctions, MapArrayFunction, MapElement, MapJsonAsync, MapObject, } from './models/MapperFunctions.js'; const formatResult: FormatResult = (value, $formatting, $root) => isWireFunction($formatting) ? $formatting(value, $root) : isObject($formatting) ? mapObject(value, $formatting, $root) : value; const handleMapperFunctions: HandleMapperFunctions = async ( [k, v], json, $root ) => { // store results from various stages of the evaluations // eventually reducing this to a finalVal let val, formatted, finalVal: any; // if (v) { // evaluate a given string as a jsonpath expression if (isString(v.$path)) { val = jpath(v.$path, json); } if (isArray(v.$path)) { val = await tryMultiple(json, v.$path, $root, findMultiple); } if (!isUndefined(v.$formatting) && !isNullOrUndefined(val)) { if (isArray(val)) { formatted = await Promise.all( val.map( async inner => !isUndefined(v.$formatting) && (await formatResult(inner, v.$formatting, $root)) ) ); } else { formatted = await formatResult(val, v.$formatting, $root); } val = formatted; } if (v.$return) { finalVal = await formatResult(val, v.$return, $root); } else { finalVal = val; } if (!isUndefined(v.$default) && isNullOrUndefined(finalVal)) { if (isWireFunction(v.$default)) { finalVal = await v.$default(json, $root); } else { finalVal = v.$default; } } if ( v.$disable && isWireFunction(v.$disable) && (await v.$disable(finalVal, $root)) ) { return null; } return finalVal; }; const mapElement: MapElement = async ([k, v], json, $root) => { if (isNullOrUndefined(v) || v === '') return [k, undefined as any]; // evaluate a given string as a jsonpath expression if (isString(v)) { return [k, jpath(v, json)]; } // execute a given function if (isWireFunction(v)) { return [k, await v(json, $root)]; } // evaluate all the array strings as jsonpath // and then return the first match if (isArray(v)) { return [k, await tryMultiple(json, v, $root, findMultiple)]; } // if typeof Object, this could be a template object // look for reserved key $path and it can be combined with // any of $formatting, $return, $default, $disable // if not we will treat as plain object and call self recursively if (isMapperFunctions(v)) { return [k, await handleMapperFunctions([k, v], json, $root)]; } // Otherwise we have ourselves a nested object if (typeof v === 'object') return [k, await mapObject(json, v, $root)]; return [k, v]; }; const mapObject: MapObject = async (json, obj, $root) => { const objectAsEntries = ( await Promise.all( // For each key value entry in the object, evaluate each entry // and make the mapping from the provided json according to the // supplied object template that follows a few simple principles Object.entries(obj).map( async ([k, v]) => await mapElement([k, v], json, $root) ) ) ).filter(([, v]) => v !== null); return fromEntries(objectAsEntries); }; const mapArray: MapArrayFunction = async <S, T>( json: S, arr: MappingTemplate<S>[], $root: S ) => { return ((await Promise.all( arr.map(async v => await mapObject(json, v, $root)) )) as unknown) as T; }; const findMultiple = <S>( json: S, arr: MappingElement<S>[], $root: S ): any[] => { const results = arr.map(async (inner, i) => { // evaluate any value supplied as string if (isString(inner)) return jpath(inner, json); // if typeof func if (isWireFunction(inner)) return await inner(json, $root); // if typeof array if (isArray(inner)) return await tryMultiple(json, inner, $root, findMultiple); // if typeof mapper functions if (isMapperFunctions(inner)) return await handleMapperFunctions([i.toString(), inner], json, $root); // if typeof plain object if (typeof inner === 'object') return await mapObject(json, inner as MappingTemplate<S>, $root); }); return results; }; const tryMultiple = async <S>( json: S, arr: MappingElement<S>[], $root: S, findMultiple: (json: S, arr: MappingElement<S>[], $root: S) => any[] ) => { const result = (await Promise.all(findMultiple(json, arr, $root))) .filter(r => isNumber(r) || r); if (arr.every((i) => typeof i === 'string')) return result.length > 0 ? result[0] : null; return result; }; const mapJson: MapJsonAsync = async (json, template) => { const $root = json; // console.log(template); if (typeof template === 'function') { return await template(json); } if (isArray(template)) { // console.log(mapArray(json, template)); return await mapArray(json, template, $root); } // console.log(mapObject(json, template)); return await mapObject(json, template, $root); }; export const useMapper: MapJsonAsync = async (json, template) => { return await mapJson(json, template); }; export default mapJson;