UNPKG

@deck.gl/json

Version:

JSON format rendering components for deck.gl

191 lines (155 loc) 5.9 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors // Converts JSON to props ("hydrating" classes, resolving enums and functions etc). // Lightly processes `json` props, transform string values, and extract `views` and `layers` // See: https://github.com/visgl/deck.gl/blob/master/dev-docs/RFCs/v6.1/json-layers-rfc.md // // NOTES: // * This is intended to provide minimal necessary processing required to support // existing deck.gl props via JSON. This is not an implementation of alternate JSON schemas. // * Optionally, error checking could be applied, but ideally should leverage // non-JSON specific mechanisms like prop types. import assert from './utils/assert'; import JSONConfiguration from './json-configuration'; import {instantiateClass} from './helpers/instantiate-class'; import {executeFunction} from './helpers/execute-function'; import {FUNCTION_IDENTIFIER, CONSTANT_IDENTIFIER, FUNCTION_KEY} from './syntactic-sugar'; import parseJSON from './helpers/parse-json'; const isObject = value => value && typeof value === 'object'; export type JSONConverterProps = { configuration: JSONConfiguration | Record<string, any>; onJSONChange; }; export default class JSONConverter { log = console; // eslint-disable-line configuration!: JSONConfiguration; onJSONChange = () => {}; json = null; convertedJson = null; constructor(props) { this.setProps(props); } // eslint-disable-next-line @typescript-eslint/no-empty-function finalize() {} setProps(props: JSONConverterProps) { // HANDLE CONFIGURATION PROPS if ('configuration' in props) { // Accept object or `JSONConfiguration` this.configuration = props.configuration instanceof JSONConfiguration ? props.configuration : new JSONConfiguration(props.configuration); } if ('onJSONChange' in props) { this.onJSONChange = props.onJSONChange; } } mergeConfiguration(config) { this.configuration.merge(config); } convert(json) { // Use shallow equality to ensure we only convert same json once if (!json || json === this.json) { return this.convertedJson; } // Save json for shallow diffing this.json = json; // Accept JSON strings by parsing them const parsedJSON = parseJSON(json); // Convert the JSON let convertedJson = convertJSON(parsedJSON, this.configuration); convertedJson = this.configuration.postProcessConvertedJson(convertedJson); this.convertedJson = convertedJson; return convertedJson; } // DEPRECATED: Backwards compatibility convertJson(json) { return this.convert(json); } } function convertJSON(json, configuration) { // Fixup configuration configuration = new JSONConfiguration(configuration); return convertJSONRecursively(json, '', configuration); } // Converts JSON to props ("hydrating" classes, resolving enums and functions etc). function convertJSONRecursively(json, key, configuration) { if (Array.isArray(json)) { return json.map((element, i) => convertJSONRecursively(element, String(i), configuration)); } // If object.type is in configuration, instantiate if (isClassInstance(json, configuration)) { return convertClassInstance(json, configuration); } if (isObject(json)) { // If object.function is in configuration, convert object to function if (FUNCTION_KEY in json) { return convertFunctionObject(json, configuration); } return convertPlainObject(json, configuration); } // Single value if (typeof json === 'string') { return convertString(json, key, configuration); } // Return unchanged (number, boolean, ...) return json; } // Returns true if an object has a `type` field function isClassInstance(json, configuration) { const {typeKey} = configuration; const isClass = isObject(json) && Boolean(json[typeKey]); return isClass; } function convertClassInstance(json, configuration) { // Extract the class type field const {typeKey} = configuration; const type = json[typeKey]; // Prepare a props object and ensure all values have been converted let props = {...json}; delete props[typeKey]; props = convertPlainObject(props, configuration); return instantiateClass(type, props, configuration); } // Plain JS object, embed functions. function convertFunctionObject(json, configuration) { // Extract the target function field const {functionKey} = configuration; const targetFunction = json[functionKey]; // Prepare a props object and ensure all values have been converted let props = {...json}; delete props[functionKey]; props = convertPlainObject(props, configuration); return executeFunction(targetFunction, props, configuration); } // Plain JS object, convert each key and return. function convertPlainObject(json, configuration) { assert(isObject(json)); const result = {}; for (const key in json) { const value = json[key]; result[key] = convertJSONRecursively(value, key, configuration); } return result; } // Convert one string value in an object // TODO - We could also support string syntax for hydrating other types, like regexps... // But no current use case function convertString(string, key, configuration) { // Here the JSON value is supposed to be treated as a function if (string.startsWith(FUNCTION_IDENTIFIER) && configuration.convertFunction) { string = string.replace(FUNCTION_IDENTIFIER, ''); return configuration.convertFunction(string, configuration); } if (string.startsWith(CONSTANT_IDENTIFIER)) { string = string.replace(CONSTANT_IDENTIFIER, ''); if (configuration.constants[string]) { return configuration.constants[string]; } // enum const [enumVarName, enumValName] = string.split('.'); return configuration.enumerations[enumVarName][enumValName]; } return string; }