Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import jsep from 'jsep';

/**
* Sources:
* - Copyright (c) 2013 Stephen Oney, http://jsep.from.so/, MIT License
* - Copyright (c) 2023 Don McCurdy, https://github.com/donmccurdy/expression-eval, MIT License
*/

// Default operator precedence from https://github.com/EricSmekens/jsep/blob/master/src/jsep.js#L55
import jsep from 'jsep';

/** Default operator precedence from https://github.com/EricSmekens/jsep/blob/master/src/jsep.js#L55 */
const DEFAULT_PRECEDENCE = {
'||': 1,
'&&': 2,
Expand Down
11 changes: 7 additions & 4 deletions modules/json/src/helpers/convert-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import parseExpressionString from './parse-expression-string';
import {parseExpressionString} from './parse-expression-string';

import {FUNCTION_IDENTIFIER} from '../syntactic-sugar';
import {type JSONConfiguration} from '../json-configuration';

function hasFunctionIdentifier(value) {
return typeof value === 'string' && value.startsWith(FUNCTION_IDENTIFIER);
Expand All @@ -14,9 +15,11 @@ function trimFunctionIdentifier(value) {
return value.replace(FUNCTION_IDENTIFIER, '');
}

// Try to determine if any props are function valued
// and if so convert their string values to functions
export default function convertFunctions(props, configuration) {
/**
* Tries to determine if any props are "function valued"
* and if so convert their string values to functions
*/
export function convertFunctions(props, configuration: JSONConfiguration) {
// Use deck.gl prop types if available.
const replacedProps = {};
for (const propName in props) {
Expand Down
12 changes: 8 additions & 4 deletions modules/json/src/helpers/execute-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

// This attempts to execute a function
export function executeFunction(targetFunction, props, configuration) {
import {type JSONConfiguration} from '../json-configuration';

/**
* Attempt to execute a function
*/
export function executeFunction(targetFunction, props, configuration: JSONConfiguration) {
// Find the function
const matchedFunction = configuration.functions[targetFunction];
const matchedFunction = configuration.config.functions[targetFunction];

// Check that the function is in the configuration.
if (!matchedFunction) {
const {log} = configuration; // eslint-disable-line
const {log} = configuration.config; // eslint-disable-line
if (log) {
const stringProps = JSON.stringify(props, null, 0).slice(0, 40);
log.warn(`JSON converter: No registered function ${targetFunction}(${stringProps}...) `);
Expand Down
25 changes: 14 additions & 11 deletions modules/json/src/helpers/instantiate-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import convertFunctions from './convert-functions';
import {JSONConfiguration} from '../json-configuration';
import {convertFunctions} from './convert-functions';

// This attempts to instantiate a class, either as a class or as a React component
export function instantiateClass(type, props, configuration) {
/**
* Attempt to instantiate a class, either as a class or as a React component
*/
export function instantiateClass(type, props, configuration: JSONConfiguration) {
// Find the class
const Class = configuration.classes[type];
const Component = configuration.reactComponents[type];
const Class = configuration.config.classes[type];
const Component = configuration.config.reactComponents[type];

// Check that the class is in the configuration.
if (!Class && !Component) {
const {log} = configuration; // eslint-disable-line
const {log} = configuration.config; // eslint-disable-line
if (log) {
const stringProps = JSON.stringify(props, null, 0).slice(0, 40);
log.warn(`JSON converter: No registered class of type ${type}(${stringProps}...) `);
Expand All @@ -27,20 +30,20 @@ export function instantiateClass(type, props, configuration) {
return instantiateReactComponent(Component, props, configuration);
}

function instantiateJavaScriptClass(Class, props, configuration) {
function instantiateJavaScriptClass(Class, props, configuration: JSONConfiguration) {
if (configuration.preProcessClassProps) {
props = configuration.preProcessClassProps(Class, props, configuration);
props = configuration.preProcessClassProps(Class, props);
}
props = convertFunctions(props, configuration);
return new Class(props);
}

function instantiateReactComponent(Component, props, configuration) {
const {React} = configuration;
function instantiateReactComponent(Component, props, configuration: JSONConfiguration) {
const {React} = configuration.config;
const {children = []} = props;
delete props.children;
if (configuration.preProcessClassProps) {
props = configuration.preProcessClassProps(Component, props, configuration);
props = configuration.preProcessClassProps(Component, props);
}

props = convertFunctions(props, configuration);
Expand Down
13 changes: 7 additions & 6 deletions modules/json/src/helpers/parse-expression-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
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';
import {parse, eval as evaluate} from '../expression-eval/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) {
/** Generates an accessor function from a JSON string
* '-' : x => x
* 'a.b.c': x => x.a.b.c
*/
export 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];
Expand Down Expand Up @@ -47,7 +48,7 @@ export default function parseExpressionString(propValue, configuration) {
return func;
}

// Helper function to search all nodes in AST returned by expressionEval
/** Helper function to search all nodes in AST returned by expressionEval */
// eslint-disable-next-line complexity
function traverse(node, visitor) {
if (Array.isArray(node)) {
Expand Down
2 changes: 1 addition & 1 deletion modules/json/src/helpers/parse-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

// Accept JSON strings by parsing them
// TODO - use a parser that provides meaninful error messages
export default function parseJSON(json) {
export function parseJSON(json) {
return typeof json === 'string' ? JSON.parse(json) : json;
}
10 changes: 5 additions & 5 deletions modules/json/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
// @deck.gl/json: top-level exports

// Generic JSON converter, usable by other wrapper modules
export {default as JSONConverter} from './json-converter';
export {default as JSONConfiguration} from './json-configuration';
export {JSONConverter, type JSONConverterProps} from './json-converter';
export {JSONConfiguration, type JSONConfigurationProps} from './json-configuration';

// Transports
export {default as Transport} from './transports/transport';
export {Transport} from './transports/transport';

// Helpers
export {default as _convertFunctions} from './helpers/convert-functions';
export {default as _parseExpressionString} from './helpers/parse-expression-string';
export {convertFunctions as _convertFunctions} from './helpers/convert-functions';
export {parseExpressionString as _parseExpressionString} from './helpers/parse-expression-string';
export {shallowEqualObjects as _shallowEqualObjects} from './utils/shallow-equal-objects';
60 changes: 32 additions & 28 deletions modules/json/src/json-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,45 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

// TODO - default parsing code should not be part of the configuration.
import parseExpressionString from './helpers/parse-expression-string';
import assert from './utils/assert';

import {TYPE_KEY, FUNCTION_KEY} from './syntactic-sugar';
// TODO - default parsing code should not be part of the configuration.
import {parseExpressionString} from './helpers/parse-expression-string';

const isObject = value => value && typeof value === 'object';

export default class JSONConfiguration {
typeKey = TYPE_KEY;
functionKey = FUNCTION_KEY;
log = console; // eslint-disable-line
classes = {};
reactComponents = {};
enumerations = {};
constants = {};
functions = {};
React = null;
export type JSONConfigurationProps = {
typeKey?: string;
functionKey?: string;
log?; // eslint-disable-line
classes?: Record<string, any>;
reactComponents?: Record<string, any>;
enumerations?: Record<string, any>;
constants: Record<string, any>;
functions: Record<string, any>;
React: Record<string, any>;
};

export class JSONConfiguration {
static defaultProps: Required<JSONConfigurationProps> = {
typeKey: TYPE_KEY,
functionKey: FUNCTION_KEY,
log: console, // eslint-disable-lin,
classes: {},
reactComponents: {},
enumerations: {},
constants: {},
functions: {},
React: undefined!
};

config: Required<JSONConfigurationProps> = {...JSONConfiguration.defaultProps};

// TODO - this needs to be simpler, function conversion should be built in
convertFunction = parseExpressionString;
preProcessClassProps = (Class, props) => props;
postProcessConvertedJson = json => json;

constructor(...configurations) {
constructor(...configurations: JSONConfigurationProps[]) {
for (const configuration of configurations) {
this.merge(configuration);
}
Expand All @@ -34,24 +49,13 @@ export default class JSONConfiguration {
merge(configuration) {
for (const key in configuration) {
switch (key) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the switch necessary?

// DEPRECATED = For backwards compatibility, add views and layers to classes;
case 'layers':
case 'views':
Object.assign(this.classes, configuration[key]);
break;
default:
// Store configuration as root fields (this.classes, ...)
if (key in this) {
if (key in this.config) {
const value = configuration[key];
this[key] = isObject(this[key]) ? Object.assign(this[key], value) : value;
this[key] = isObject(this.config[key]) ? Object.assign(this.config[key], value) : value;
}
}
}
}

validate(configuration) {
assert(!this.typeKey || typeof this.typeKey === 'string');
assert(isObject(this.classes));
return true;
}
}
Loading
Loading