123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import { isArray, isEmptyObject, isMap, isPlainObject, isPrimitive, isSet, } from './is.js';
- import { escapeKey, stringifyPath } from './pathstringifier.js';
- import { isInstanceOfRegisteredClass, transformValue, untransformValue, } from './transformer.js';
- import { includes, forEach } from './util.js';
- import { parsePath } from './pathstringifier.js';
- import { getDeep, setDeep } from './accessDeep.js';
- function traverse(tree, walker, origin = []) {
- if (!tree) {
- return;
- }
- if (!isArray(tree)) {
- forEach(tree, (subtree, key) => traverse(subtree, walker, [...origin, ...parsePath(key)]));
- return;
- }
- const [nodeValue, children] = tree;
- if (children) {
- forEach(children, (child, key) => {
- traverse(child, walker, [...origin, ...parsePath(key)]);
- });
- }
- walker(nodeValue, origin);
- }
- export function applyValueAnnotations(plain, annotations, superJson) {
- traverse(annotations, (type, path) => {
- plain = setDeep(plain, path, v => untransformValue(v, type, superJson));
- });
- return plain;
- }
- export function applyReferentialEqualityAnnotations(plain, annotations) {
- function apply(identicalPaths, path) {
- const object = getDeep(plain, parsePath(path));
- identicalPaths.map(parsePath).forEach(identicalObjectPath => {
- plain = setDeep(plain, identicalObjectPath, () => object);
- });
- }
- if (isArray(annotations)) {
- const [root, other] = annotations;
- root.forEach(identicalPath => {
- plain = setDeep(plain, parsePath(identicalPath), () => plain);
- });
- if (other) {
- forEach(other, apply);
- }
- }
- else {
- forEach(annotations, apply);
- }
- return plain;
- }
- const isDeep = (object, superJson) => isPlainObject(object) ||
- isArray(object) ||
- isMap(object) ||
- isSet(object) ||
- isInstanceOfRegisteredClass(object, superJson);
- function addIdentity(object, path, identities) {
- const existingSet = identities.get(object);
- if (existingSet) {
- existingSet.push(path);
- }
- else {
- identities.set(object, [path]);
- }
- }
- export function generateReferentialEqualityAnnotations(identitites, dedupe) {
- const result = {};
- let rootEqualityPaths = undefined;
- identitites.forEach(paths => {
- if (paths.length <= 1) {
- return;
- }
- // if we're not deduping, all of these objects continue existing.
- // putting the shortest path first makes it easier to parse for humans
- // if we're deduping though, only the first entry will still exist, so we can't do this optimisation.
- if (!dedupe) {
- paths = paths
- .map(path => path.map(String))
- .sort((a, b) => a.length - b.length);
- }
- const [representativePath, ...identicalPaths] = paths;
- if (representativePath.length === 0) {
- rootEqualityPaths = identicalPaths.map(stringifyPath);
- }
- else {
- result[stringifyPath(representativePath)] = identicalPaths.map(stringifyPath);
- }
- });
- if (rootEqualityPaths) {
- if (isEmptyObject(result)) {
- return [rootEqualityPaths];
- }
- else {
- return [rootEqualityPaths, result];
- }
- }
- else {
- return isEmptyObject(result) ? undefined : result;
- }
- }
- export const walker = (object, identities, superJson, dedupe, path = [], objectsInThisPath = [], seenObjects = new Map()) => {
- const primitive = isPrimitive(object);
- if (!primitive) {
- addIdentity(object, path, identities);
- const seen = seenObjects.get(object);
- if (seen) {
- // short-circuit result if we've seen this object before
- return dedupe
- ? {
- transformedValue: null,
- }
- : seen;
- }
- }
- if (!isDeep(object, superJson)) {
- const transformed = transformValue(object, superJson);
- const result = transformed
- ? {
- transformedValue: transformed.value,
- annotations: [transformed.type],
- }
- : {
- transformedValue: object,
- };
- if (!primitive) {
- seenObjects.set(object, result);
- }
- return result;
- }
- if (includes(objectsInThisPath, object)) {
- // prevent circular references
- return {
- transformedValue: null,
- };
- }
- const transformationResult = transformValue(object, superJson);
- const transformed = transformationResult?.value ?? object;
- const transformedValue = isArray(transformed) ? [] : {};
- const innerAnnotations = {};
- forEach(transformed, (value, index) => {
- if (index === '__proto__' ||
- index === 'constructor' ||
- index === 'prototype') {
- throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
- }
- const recursiveResult = walker(value, identities, superJson, dedupe, [...path, index], [...objectsInThisPath, object], seenObjects);
- transformedValue[index] = recursiveResult.transformedValue;
- if (isArray(recursiveResult.annotations)) {
- innerAnnotations[index] = recursiveResult.annotations;
- }
- else if (isPlainObject(recursiveResult.annotations)) {
- forEach(recursiveResult.annotations, (tree, key) => {
- innerAnnotations[escapeKey(index) + '.' + key] = tree;
- });
- }
- });
- const result = isEmptyObject(innerAnnotations)
- ? {
- transformedValue,
- annotations: !!transformationResult
- ? [transformationResult.type]
- : undefined,
- }
- : {
- transformedValue,
- annotations: !!transformationResult
- ? [transformationResult.type, innerAnnotations]
- : innerAnnotations,
- };
- if (!primitive) {
- seenObjects.set(object, result);
- }
- return result;
- };
- //# sourceMappingURL=plainer.js.map
|