shared-updated
Version:
Modern fork of shared (Kevin Jones), updated for latest Node.js and MongoDB
310 lines (270 loc) • 8.59 kB
text/typescript
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='utils.ts' />
/// <reference path='id.ts' />
module shared {
export module serial {
/*
* Object/Array reference holder. Used to represent a reference when
* de-serialising data.
*/
export class Reference {
private _id: utils.uid;
constructor (id: utils.uid) {
utils.dassert(utils.isUID(id));
this._id = id;
}
id(): utils.uid {
return this._id;
}
}
/*
* Reference Handler interface used to obtain a unique ID to be used
* when serializing values of composite types. A valid UID must always
* be returned and must be consistent for the value.
*/
export interface ReferenceHandler {
valueId(value: any): utils.uid;
valueRev(value: any): number;
}
/*
* Append serialized form of an object/array onto the supplied string.
* Returns the passed string.
*/
export function writeObject(rh: ReferenceHandler, obj: any, to: string = '', identify: bool = false) {
utils.dassert(utils.isObjectOrArray(rh));
utils.dassert(utils.isObjectOrArray(obj));
if (obj instanceof Array) {
to += '[';
} else {
to += '{';
}
if (identify) {
to += rh.valueId(obj) + ' ';
to += rh.valueRev(obj) + ' ';
}
var k = Object.keys(obj);
for (var i = 0; i < k.length; i++) {
to = writeValue(rh, k[i], to);
to += ":";
to = writeValue(rh, obj[k[i]], to);
if (i < k.length - 1)
to += ',';
}
if (obj instanceof Array) {
to += ']';
} else {
to += '}';
}
return to;
}
/*
* Append serialized form of a value onto the supplied string.
* Object/Array values are serialised by reference, see writeObject() for
* full serialisation of object/array properties. Returns the passed string.
*/
export function writeValue(rh: ReferenceHandler, value: any, to: string = ''): string {
utils.dassert(utils.isObject(rh));
var type = utils.treatAs(value);
switch (type) {
case 'null':
to += 'null';
break;
case 'undefined':
to += 'undefined';
break;
case 'number':
case 'Number':
case 'boolean':
case 'Boolean':
to += value.toString();
break;
case 'string':
case 'String':
to += JSON.stringify(value);
break;
case 'Date':
to += JSON.stringify(value.toString());
break;
case 'Object':
case 'Array':
to += '<' + rh.valueId(value) + '>';
break;
case 'function':
case 'RegExp':
case 'Error':
to += 'null';
break;
default:
utils.defaultLogger().fatal('Unexpected type: %s', type);
break;
}
return to;
}
export function readObject(str: string, proto?: any) : any {
utils.dassert(str.length >1 &&
(str.charAt(0) === '[' || str.charAt(0) === '{') &&
(str.charAt(str.length-1) === ']' || str.charAt(str.length-1) === '}'))
// Check is we have a proto & its the right type
if (str.charAt(0) === '{') {
if (!utils.isValue(proto)) {
proto = {};
} else {
utils.dassert(utils.isObject(proto));
}
} else {
if (!utils.isValue(proto)) {
proto = [];
} else {
utils.dassert(utils.isArray(proto));
// Prop delete does not work well on arrays so zero proto
proto.length = 0;
}
}
// Read props
var rs = new ReadStream(str.substr(1,str.length-2));
var keys = Object.keys(proto);
var k = 0;
while (true) {
rs.skipWS();
if (rs.eof())
break;
// Read prop name
var prop = rs.readNextValue();
utils.dassert(typeof prop === 'string');
// Delete rest of proto props if does not match what is being read
if (k !== -1 && prop != keys[k]) {
for (var i = k; i < keys.length; i++)
delete proto[keys[i]];
k = -1;
}
// Skip ':'
rs.skipWS();
utils.dassert(!rs.eof());
utils.dassert(rs.peek()===':');
rs.skip();
rs.skipWS();
// Read value & assign
var value = rs.readNextValue();
proto[prop] = value;
// Skip ',' if present
rs.skipWS();
if (!rs.eof()) {
utils.dassert(rs.peek() === ',');
rs.skip();
rs.skipWS();
} else {
break;
}
}
return proto;
}
/*
* Read a value as encoded by writeValue. The passed string must contain
* one complete value with no leading or trailing characters. May return
* null if passed 'null'.
*/
export function readValue(str: string): any {
utils.dassert(utils.isValue(str));
var rs = new ReadStream(str);
return rs.readNextValue();
}
class ReadStream {
private _from: string;
private _at: number;
private static _numberPat = /^-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+][0-9]+)?/;
constructor (from: string) {
utils.dassert(utils.isValue(from));
this._from = from;
this._at = 0;
}
eof(): bool {
return this._at >= this._from.length;
}
skip(n: number=1) {
this._at += n;
}
skipWS() {
while (this._at < this._from.length && (
this._from[this._at] === ' ' || this._from[this._at] === '\t')) {
this._at++;
}
}
peek(n: number=0) {
utils.dassert(this._at + n < this._from.length);
return this._from[this._at + n];
}
/*
* Read a value as encoded by writeValue. The passed string must contain
* one complete value with no leading or trailing characters. May return
* null if passed 'null'.
*/
readNextValue(): any {
// Simple things first
if (this._from.substr(this._at, 4) === 'null') {
this._at += 4;
return null;
} else if (this._from.substr(this._at, 9) === 'undefined') {
this._at += 9;
return undefined;
} else if(this._from.substr(this._at, 4) === 'true') {
this._at += 4;
return true;
} else if(this._from.substr(this._at, 5) === 'false') {
this._at += 5;
return false;
} else if(this._from.substr(this._at, 3) === 'NaN') {
this._at += 3;
return NaN;
} else if(this._from.substr(this._at, 8) === 'Infinity') {
this._at += 8;
return Infinity;
} else if(this._from.substr(this._at, 9) === '-Infinity') {
this._at += 9;
return -Infinity;
}
// JSON escaped string?
if (this._from.charAt(this._at) === '"') {
var end = this._at + 1;
while (end < this._from.length) {
if (this._from.charAt(end) === '\\')
end += 1;
else if (this._from.charAt(end) === '"')
break;
end += 1;
}
if (end < this._from.length) {
var s = this._from.substr(this._at, end - this._at +1);
this._at = end + 1;
return JSON.parse(s);
}
}
// Reference?
if (this._from.charAt(this._at) === '<' &&
this._from.charAt(this._at+1+utils.uidStringLength) === '>') {
var id = this._from.substr(this._at+1, utils.uidStringLength);
this._at += (2+utils.uidStringLength);
return new Reference(utils.makeUID(id));
}
// Maybe a number
var l = this.numberLength();
if (l) {
var n = parseFloat(this._from.substr(this._at));
utils.dassert(!isNaN(n));
this._at += l;
return n;
}
utils.defaultLogger().fatal('Unexpected value encoding: %s', this._from.substr(this._at));
}
numberLength() {
var ex = ReadStream._numberPat.exec(this._from.substr(this._at));
if (ex)
return ex[0].length;
else
return 0;
}
}
} // tracker
} // shared