135 lines
3.3 KiB
JavaScript
135 lines
3.3 KiB
JavaScript
import * as CF from '@efforting.tech/schema/field-configuration-factories';
|
|
|
|
|
|
export const Object_Serialization_Strategy = new CF.Schema({
|
|
identity: CF.boolean(true, 'Serialize based on identity'),
|
|
type: CF.boolean(true, 'Serialize based on constructor'),
|
|
symbols: CF.boolean(true, 'Include symbol-keyed properties'),
|
|
keys: CF.boolean(true, 'Include property keys'),
|
|
values: CF.boolean(true, 'Include property values'),
|
|
}, 'Object serialization strategy');
|
|
|
|
|
|
|
|
export const State_Serialization_Strategy = new CF.Schema({
|
|
//BUG - there is currently no way (I think) to put defaults into a sub schema - this should be fixed
|
|
array: CF.schema(Object_Serialization_Strategy, {
|
|
identity: false,
|
|
type: true,
|
|
symbols: false,
|
|
keys: false,
|
|
values: true,
|
|
}, 'State serialization strategy for Array'),
|
|
object: CF.schema(Object_Serialization_Strategy, {
|
|
identity: true,
|
|
type: true,
|
|
symbols: true,
|
|
keys: true,
|
|
values: true,
|
|
}, 'State serialization strategy for Object'),
|
|
}, 'State serialization strategy');
|
|
|
|
|
|
|
|
|
|
export class State_Serializer {
|
|
|
|
static settings_schema = State_Serialization_Strategy;
|
|
|
|
constructor(symbols=new Map(), strategy) {
|
|
strategy = this.constructor.settings_schema.load(strategy);
|
|
Object.assign(this, { symbols, strategy });
|
|
}
|
|
|
|
serialize_symbols(...symbols) {
|
|
const result = [];
|
|
for (const symbol of symbols) {
|
|
const existing = this.symbols.get(symbol);
|
|
if (existing !== undefined) {
|
|
result.push(existing);
|
|
} else {
|
|
const new_symbol_index = this.symbols.size;
|
|
this.symbols.set(symbol, new_symbol_index);
|
|
result.push(new_symbol_index);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
serialize_object(object) {
|
|
const state_strategy = this.strategy;
|
|
const result = [];
|
|
|
|
switch (typeof(object)) {
|
|
case 'object':
|
|
if (object === null) {
|
|
return ['N'];
|
|
}
|
|
|
|
|
|
const is_array = object.constructor === Array;
|
|
const strategy = is_array ? state_strategy.array : state_strategy.object;
|
|
result.push(is_array ? 'a' : 'o');
|
|
|
|
|
|
if (strategy.identity) {
|
|
const existing_instance = this.symbols.get(object);
|
|
if (existing_instance !== undefined) {
|
|
return existing_instance;
|
|
}
|
|
|
|
const [instance] = this.serialize_symbols(object);
|
|
result.push(instance);
|
|
}
|
|
|
|
if (strategy.type) {
|
|
const [type] = this.serialize_symbols(object.constructor);
|
|
result.push(type);
|
|
}
|
|
|
|
if (strategy.symbols) {
|
|
const symbols = this.serialize_symbols(...Object.getOwnPropertySymbols(object));
|
|
result.push(symbols);
|
|
}
|
|
|
|
if (strategy.keys) {
|
|
const keys = Object.keys(object).map(item => this.serialize_object(item));
|
|
result.push(keys);
|
|
}
|
|
|
|
if (strategy.values) {
|
|
const values = Object.values(object).map(item => this.serialize_object(item));
|
|
result.push(values);
|
|
}
|
|
|
|
return result;
|
|
|
|
case 'string':
|
|
return ['s', ...this.serialize_symbols(typeof(object)), object];
|
|
|
|
case 'number':
|
|
return ['n', ...this.serialize_symbols(typeof(object)), object];
|
|
|
|
case 'boolean':
|
|
return [object ? 'T' : 'F'];
|
|
|
|
case 'function':
|
|
const [reference_type, reference_id] = this.serialize_symbols(typeof(object), object);
|
|
return ['f', reference_type, reference_id];
|
|
|
|
case 'symbol':
|
|
return ['S', ...this.serialize_symbols(object)];
|
|
|
|
|
|
case 'undefined':
|
|
return ['u'];
|
|
|
|
|
|
default:
|
|
throw new Error(typeof(object)); //TODO - proper error
|
|
}
|
|
}
|
|
};
|
|
|