@deck.gl/json
Version:
JSON format rendering components for deck.gl
64 lines (56 loc) • 1.89 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import {get} from '../utils/get';
// expression-eval: Small jsep based expression parser that supports array and object indexing
import {parse, eval as evaluate} from '../utils/expression-eval';
const cachedExpressionMap = {
'-': object => object
};
// Calculates an accessor function from a JSON string
// '-' : x => x
// 'a.b.c': x => x.a.b.c
export default function parseExpressionString(propValue, configuration) {
// NOTE: Can be null which represents invalid function. Return null so that prop can be omitted
if (propValue in cachedExpressionMap) {
return cachedExpressionMap[propValue];
}
let func;
// Compile with expression-eval
const ast = parse(propValue);
if (ast.type === 'Identifier') {
func = row => {
return get(row, propValue);
};
} else {
// NOTE: To avoid security risks, the arguments passed to the
// compiled expression must only give access to pure data (no globals etc)
// We disable function call syntax
traverse(ast, node => {
if (node.type === 'CallExpression') {
throw new Error('Function calls not allowed in JSON expressions');
}
});
// TODO Something like `expressionEval.eval(ast, {row});` would be useful for unpacking arrays
func = row => {
return evaluate(ast, row);
};
}
// Cache the compiled function
cachedExpressionMap[propValue] = func;
return func;
}
// Helper function to search all nodes in AST returned by expressionEval
// eslint-disable-next-line complexity
function traverse(node, visitor) {
if (Array.isArray(node)) {
node.forEach(element => traverse(element, visitor));
} else if (node && typeof node === 'object') {
if (node.type) {
visitor(node);
}
for (const key in node) {
traverse(node[key], visitor);
}
}
}