UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

324 lines 11.6 kB
import { logger } from "../logging"; import { is_ref } from "../util/refs"; import { ndarray } from "../util/ndarray"; import { entries } from "../util/object"; import { map } from "../util/array"; import { BYTE_ORDER } from "../util/platform"; import { base64_to_buffer, swap } from "../util/buffer"; import { isArray, isPlainObject, isString, isNumber } from "../util/types"; import { Slice } from "../util/slice"; const _decoders = new Map(); export class DeserializationError extends Error { static __name__ = "DeserializationError"; } export class Deserializer { resolver; references; finalize; static __name__ = "Deserializer"; static register(type, decoder) { if (!_decoders.has(type)) { _decoders.set(type, decoder); } else { throw new Error(`'${type}' already registered for decoding`); } } constructor(resolver, references = new Map(), finalize) { this.resolver = resolver; this.references = references; this.finalize = finalize; } _decoding = false; _buffers = new Map(); _finalizable = new Set(); decode(obj /*AnyVal*/, buffers) { if (buffers != null) { for (const [id, buffer] of buffers) { this._buffers.set(id, buffer); } } if (this._decoding) { return this._decode(obj); } this._decoding = true; let finalizable; const decoded = (() => { try { return this._decode(obj); } finally { finalizable = new Set(this._finalizable); this._decoding = false; this._buffers.clear(); this._finalizable.clear(); } })(); for (const instance of finalizable) { this.finalize?.(instance); instance.finalize(); instance.assert_initialized(); } // `connect_signals` has to be executed last because it may rely on properties // of dependencies that are initialized only in `finalize`. It's a problem // that appears when there are circular references, e.g. as in // CDS -> CustomJS (on data change) -> GlyphRenderer (in args) -> CDS. for (const instance of finalizable) { instance.connect_signals(); } return decoded; } _decode(obj /*AnyVal*/) { if (isArray(obj)) { return this._decode_plain_array(obj); } else if (isPlainObject(obj)) { if (isString(obj.type)) { const decoder = _decoders.get(obj.type); if (decoder != null) { return decoder(obj, this); } switch (obj.type) { case "ref": return this._decode_ref(obj); case "symbol": return this._decode_symbol(obj); case "number": return this._decode_number(obj); case "array": return this._decode_array(obj); case "set": return this._decode_set(obj); case "map": return this._decode_map(obj); case "bytes": return this._decode_bytes(obj); case "slice": return this._decode_slice(obj); case "date": return this._decode_date(obj); case "value": return this._decode_value(obj); case "field": return this._decode_field(obj); case "expr": return this._decode_expr(obj); case "typed_array": return this._decode_typed_array(obj); case "ndarray": return this._decode_ndarray(obj); case "object": { if (isString(obj.id)) { return this._decode_object_ref(obj); } else { return this._decode_object(obj); } } default: { this.error(`unable to decode an object of type '${obj.type}'`); } } } else if (isString(obj.id)) { return this._decode_ref(obj); } else { return this._decode_plain_object(obj); } } else { return obj; } } _decode_symbol(obj) { this.error(`can't resolve named symbol '${obj.name}'`); // TODO: implement symbol resolution } _decode_number(obj) { if ("value" in obj) { const { value } = obj; if (isString(value)) { switch (value) { case "nan": return NaN; case "+inf": return +Infinity; case "-inf": return -Infinity; } } else if (isNumber(value)) { return value; } } this.error(`invalid number representation '${obj}'`); } _decode_plain_array(obj) { return map(obj, (item) => this._decode(item)); } _decode_plain_object(obj) { const decoded = {}; for (const [key, val] of entries(obj)) { decoded[key] = this._decode(val); } return decoded; } _decode_array(obj) { const decoded = []; for (const entry of obj.entries ?? []) { decoded.push(this._decode(entry)); } return decoded; } _decode_set(obj) { const decoded = new Set(); for (const entry of obj.entries ?? []) { decoded.add(this._decode(entry)); } return decoded; } _decode_map(obj) { const entries = map(obj.entries ?? [], ([key, val]) => [this._decode(key), this._decode(val)]); const is_plain = entries.every(([key, _val]) => isString(key)); // An empty container will result in a plain object, not a Map, thus in the case of // kinds.Mapping property type, one needs to accommodate for this in all instances. // Fortunately there are few of these scattered across `src/lib/models/`. See HACK // in `Mapping.coerce()` in `core/util/kinds`. if (is_plain) { return Object.fromEntries(entries); } else { return new Map(entries); } } _decode_bytes(obj) { const { data } = obj; if (is_ref(data)) { const buffer = this._buffers.get(data.id); if (buffer != null) { return buffer; } else { this.error(`buffer for id=${data.id} not found`); } } else if (isString(data)) { return base64_to_buffer(data); } else { return data.buffer; } } _decode_slice(obj) { const start = this._decode(obj.start); const stop = this._decode(obj.stop); const step = this._decode(obj.step); return new Slice({ start, stop, step }); } _decode_date(obj) { const iso = this._decode(obj.iso); return new Date(iso); } _decode_value(obj) { const value = this._decode(obj.value); const transform = obj.transform != null ? this._decode(obj.transform) : undefined; const units = obj.units != null ? this._decode(obj.units) : undefined; return { value, transform, units }; } _decode_field(obj) { const field = this._decode(obj.field); const transform = obj.transform != null ? this._decode(obj.transform) : undefined; const units = obj.units != null ? this._decode(obj.units) : undefined; return { field, transform, units }; } _decode_expr(obj) { const expr = this._decode(obj.expr); const transform = obj.transform != null ? this._decode(obj.transform) : undefined; const units = obj.units != null ? this._decode(obj.units) : undefined; return { expr, transform, units }; } _decode_typed_array(obj) { const { array, order, dtype } = obj; const buffer = this._decode(array); if (order != BYTE_ORDER) { swap(buffer, dtype); } switch (dtype) { case "uint8": return new Uint8Array(buffer); case "int8": return new Int8Array(buffer); case "uint16": return new Uint16Array(buffer); case "int16": return new Int16Array(buffer); case "uint32": return new Uint32Array(buffer); case "int32": return new Int32Array(buffer); // case "uint64": return new BigInt64Array(buffer) // case "int64": return new BigInt64Array(buffer) case "float32": return new Float32Array(buffer); case "float64": return new Float64Array(buffer); default: this.error(`unsupported dtype '${dtype}'`); } } _decode_ndarray(obj) { const { array, order, dtype, shape } = obj; const decoded = this._decode(array); if (decoded instanceof ArrayBuffer && order != BYTE_ORDER) { swap(decoded, dtype); } return ndarray(decoded /*XXX*/, { dtype, shape }); } _decode_object(obj) { const { name: type, attributes } = obj; const cls = this._resolve_type(type); if (attributes != null) { return new cls(this._decode(attributes)); } else { return new cls(); } } _decode_ref(obj) { const instance = this.references.get(obj.id); if (instance != null) { return instance; } else { this.error(`reference ${obj.id} isn't known`); } } _decode_object_ref(obj) { const { id, name: type, attributes } = obj; const ref = this.references.get(id); if (ref != null) { if (ref.type == type) { const decoded_attributes = this._decode(attributes ?? {}); ref.setv(decoded_attributes, { sync: false }); return ref; } else { this.error(`type mismatch for an existing reference '${ref}', expected '${type}'`); } } else { const cls = this._resolve_type(type); const instance = new cls({ id }); this.references.set(id, instance); const decoded_attributes = this._decode(attributes ?? {}); instance.initialize_props(decoded_attributes); this._finalizable.add(instance); return instance; } } error(message) { throw new DeserializationError(message); } warning(message) { logger.warn(message); } _resolve_type(type) { const cls = this.resolver.get(type); if (cls != null) { return cls; } else { this.error(`could not resolve type '${type}', which could be due to a widget or a custom model not being registered before first usage`); } } } //# sourceMappingURL=deserializer.js.map