Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pouya-eghbali committed Aug 30, 2020
1 parent c2e0110 commit 2874948
Show file tree
Hide file tree
Showing 9 changed files with 5,069 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Empty file added README.md
Empty file.
43 changes: 43 additions & 0 deletions benchmark/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const fetch = require("node-fetch");
const prettyBytes = require("pretty-bytes");
const { sia, desia } = require("..");

const runTests = (data) => {
console.log("Running SIA benchmarks");
console.log();
const bench = (serialize, deserialize, name) => {
const serstart = process.hrtime();
const serialized = serialize(data);
const serend = process.hrtime(serstart);
const deserstart = process.hrtime();
const result = deserialize(serialized);
const deserend = process.hrtime(deserstart);
console.info(`${name}:`);
console.info(
"Serialization time (hr): %ds %dms",
serend[0],
serend[1] / 1000000
);
console.info(
"Deserialization time (hr): %ds %dms",
deserend[0],
deserend[1] / 1000000
);
console.info("Char count:", serialized.length);
const size = prettyBytes(Buffer.from(serialized).length);
console.info("Size:", size);
console.log();
};

bench(sia, desia, "SIA");
bench(JSON.stringify, JSON.parse, "JSON");
};

const fileURL =
"https://github.com/json-iterator/test-data/raw/master/large-file.json";

console.log("Downloading the test data");

fetch(fileURL)
.then((resp) => resp.json())
.then(runTests);
32 changes: 32 additions & 0 deletions constructors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { decodeNumber } = require("./utils");

module.exports = {
Regex(source, flags) {
return new RegExp(source, flags);
},
Date(value) {
return new Date(decodeNumber(value));
},
Array(...args) {
return args;
},
Object(...keyValues) {
let { length } = keyValues,
index = 0;
const object = {};
while (index < length) {
object[keyValues[index]] = keyValues[index + 1];
index += 2;
}
return object;
},
Boolean(value) {
return value === "true";
},
Null() {
return null;
},
Undefined() {
return undefined;
},
};
174 changes: 174 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
const { encodeNumber, decodeNumber } = require("./utils");
const builtinConstructors = require("./constructors");

const SIA_CONSTANTS = {
TERMINATE: String.fromCharCode(0),
SEP: String.fromCharCode(1),
CONSTRUCTOR: String.fromCharCode(2),
VALUE: String.fromCharCode(3),
NUMBER: String.fromCharCode(4),
};

class SIA {
constructor(data) {
this.data = data;
this.constructorMap = new Map();
this.otherMaps = new Map();
this.stringMap = new Map();
this.numberMap = new Map();
this.linesArray = [];
}
addLine(line) {
this.linesArray.push(line);
}
getLastItemRef() {
return encodeNumber(this.linesArray.length - 1);
}
getMap(constructorRef) {
if (this.otherMaps.has(constructorRef))
return this.otherMaps.get(constructorRef);
const map = new Map();
this.otherMaps.set(constructorRef, map);
return map;
}
addConstructor(constructor) {
if (!this.constructorMap.has(constructor)) {
const { CONSTRUCTOR } = SIA_CONSTANTS;
this.addLine(CONSTRUCTOR + constructor);
const lineNumber = this.getLastItemRef();
this.constructorMap.set(constructor, lineNumber);
return lineNumber;
}
return this.constructorMap.get(constructor);
}
addValue(constructor, args) {
const constructorRef = this.addConstructor(constructor);
const map = this.getMap(constructorRef);
const { VALUE, SEP } = SIA_CONSTANTS;
const query = args.join(SEP);
if (!map.has(query)) {
const lineValue = VALUE + constructorRef + SEP + query;
this.addLine(lineValue);
const lineNumber = this.getLastItemRef();
map.set(query, lineNumber);
return lineNumber;
}
return map.get(query);
}
addString(string) {
if (!this.stringMap.has(string)) {
this.addLine(string);
const lineNumber = this.getLastItemRef();
this.stringMap.set(string, lineNumber);
return lineNumber;
}
return this.stringMap.get(string);
}
addNumber(number) {
const { NUMBER } = SIA_CONSTANTS;
if (!this.numberMap.has(number)) {
this.addLine(NUMBER + number);
const lineNumber = this.getLastItemRef();
this.numberMap.set(number, lineNumber);
return lineNumber;
}
return this.numberMap.get(number);
}
serializeItem(item) {
if (typeof item === "string") return this.addString(item);
if (typeof item === "number") return this.addNumber(item);
if (item && item.constructor === Object) {
const entries = [];
for (const key in item) entries.push(key, item[key]);
const args = entries.map((item) => this.serializeItem(item));
const constructor = "Object";
return this.addValue(constructor, args);
}
if (Array.isArray(item)) {
const args = item.map((value) => this.serializeItem(value));
const constructor = "Array";
return this.addValue(constructor, args);
}
const { constructor, args = [] } =
item && item.toSIA && typeof item.toSIA === "function"
? item.toSIA()
: this.itemToSIA(item);
const serializedArgs = args.map((value) => this.serializeItem(value));
return this.addValue(constructor, serializedArgs);
}
itemToSIA(item) {
if (item == null) {
return {
constructor: "Null",
args: [],
};
} else if (item == undefined) {
return {
constructor: "Undefined",
args: [],
};
} else if (item.constructor === Date) {
return {
constructor: "Date",
args: [encodeNumber(item.valueOf())],
};
} else if (item.constructor === RegExp) {
return {
constructor: "Regex",
args: [item.source, item.flags],
};
}
return {
constructor: item.constructor.name,
args: [item.toString()],
};
}
serialize() {
this.serializeItem(this.data);
return this.linesArray.join(SIA_CONSTANTS.TERMINATE);
}
}

class DESIA {
constructor(lines) {
this.constructorMap = new Map();
this.valueMap = new Map();
this.linesArray = lines.split(SIA_CONSTANTS.TERMINATE);
}
deserialize(constructors) {
this.constructors = constructors;
const lastLine = this.linesArray.pop();
this.linesArray.forEach((line, index) => this.parseLine(line, index));
return this.parseLine(lastLine);
}
parseLine(line, index) {
const type = line[0];
const rest = line.slice(1);
if (type == SIA_CONSTANTS.CONSTRUCTOR) {
const constructor = this.constructors[rest];
this.constructorMap.set(index, constructor);
return rest;
}
if (type == SIA_CONSTANTS.NUMBER) {
const number = parseFloat(rest);
this.valueMap.set(index, number);
return number;
}
if (type == SIA_CONSTANTS.VALUE) {
const [constructorRef, ...argRefs] = rest.split(SIA_CONSTANTS.SEP);
const constructor = this.constructorMap.get(decodeNumber(constructorRef));
const args = argRefs
.map(decodeNumber)
.map((ref) => this.valueMap.get(ref));
const value = constructor(...args);
this.valueMap.set(index, value);
return value;
}
this.valueMap.set(index, line);
return line;
}
}

module.exports.sia = (data) => new SIA(data).serialize();
module.exports.desia = (data, constructors = {}) =>
new DESIA(data).deserialize({ ...builtinConstructors, ...constructors });
Loading

0 comments on commit 2874948

Please sign in to comment.