preserves/implementations/javascript/packages/core/src/fromjs.ts

98 lines
3.3 KiB
TypeScript

import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
import { Bytes } from "./bytes";
import { Record, Tuple } from "./record";
import { Value } from "./values";
import { Dictionary, KeyedDictionary, Set } from "./dictionary";
import { JsDictionary } from "./jsdictionary";
export interface FromJSOptions<T extends Embeddable = GenericEmbedded> {
onNonInteger?(n: number): Value<T> | undefined;
}
export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T> {
return fromJS_options(x);
}
export function fromJS_options<T extends Embeddable = GenericEmbedded>(x: any, options?: FromJSOptions<T>): Value<T> {
function walk(x: any): Value<T> {
switch (typeof x) {
case 'number':
if (!Number.isInteger(x)) {
// We require that clients be explicit about integer vs. non-integer types.
const converted = options?.onNonInteger?.(x) ?? void 0;
if (converted !== void 0) return converted;
throw new TypeError("Refusing to autoconvert non-integer number to Double");
}
// FALL THROUGH
case 'bigint':
case 'string':
case 'symbol':
case 'boolean':
return x;
case 'undefined':
case 'function':
break;
case 'object':
if (x === null) {
break;
}
if (typeof x.__as_preserve__ === 'function') {
return x.__as_preserve__();
}
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
return x;
}
if (Array.isArray(x)) {
return x.map<Value<T>>(walk);
}
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
return Bytes.from(x);
}
if (Map.isMap(x)) {
const d = new KeyedDictionary<T>();
x.forEach((v, k) => d.set(walk(k), walk(v)));
return d;
}
if (Set.isSet(x)) {
const s = new Set<T>();
x.forEach(v => s.add(walk(v)));
return s;
}
if (isEmbedded<T>(x)) {
return x;
}
// Handle plain JS objects to build a JsDictionary
{
const r: JsDictionary<Value<T>> = {};
Object.entries(x).forEach(([k, v]) => r[k] = walk(v));
return r;
}
default:
break;
}
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
}
return walk(x);
}
declare module "./dictionary" {
namespace Dictionary {
export function stringMap<T extends Embeddable = GenericEmbedded>(
x: object
): KeyedDictionary<T, string, Value<T>>;
}
}
Dictionary.stringMap = function <T extends Embeddable = GenericEmbedded>(
x: object
): KeyedDictionary<T, string, Value<T>> {
const r = new KeyedDictionary<T, string, Value<T>>();
Object.entries(x).forEach(([key, value]) => r.set(key, fromJS(value)));
return r;
};