Skip to content

Instantly share code, notes, and snippets.

@shalvah
Last active January 20, 2022 14:08
Show Gist options
  • Save shalvah/46573b7c18b772ac6b4fe1bd7da3d878 to your computer and use it in GitHub Desktop.
Save shalvah/46573b7c18b772ac6b4fe1bd7da3d878 to your computer and use it in GitHub Desktop.
Custom serialization format https://blog.shalvah.me/posts/serialization
// Note: this uses Node.js' `Buffer`, so it might not work in the browser
// You can use a polyfill or write yours.
const RED = [0xFF, 0, 0, 0xFF]; // R, G, B, A
const GREEN = [0, 0xFF, 0, 0xFF];
function serialize(val, outputBytes = []) {
if (typeof val === "string") {
serializeString(val, outputBytes);
} else {
serializeObject(val, outputBytes);
}
return outputBytes;
}
function wrapInRGBA(value) {
const rgba = [0, 0, 0, 255];
const indexToUse = Math.floor(Math.random() * 3);
rgba[indexToUse] = value;
return rgba;
}
function serializeObject(obj, outputBytes = []) {
const keys = Object.keys(obj);
outputBytes.push(...RED);
outputBytes.push(...wrapInRGBA(keys.length));
keys.forEach((key) => {
serialize(key, outputBytes);
serialize(obj[key], outputBytes);
});
return outputBytes;
}
function serializeString(str, outputBytes = []) {
outputBytes.push(...GREEN);
outputBytes.push(...wrapInRGBA(str.length));
for (let i = 0; i < str.length; i++) {
outputBytes.push(...wrapInRGBA(str.charCodeAt(i)));
}
return outputBytes;
}
function parseFromString(serializedString) {
return Buffer.from(serializedString, "hex");
}
function deserialize(buffer, start = 0) {
let original;
let [type, nextIndex] = getNextFourBytes(buffer, start);
if (Array.from(type).toString() === RED.toString()) {
original = {};
let keyCount;
[keyCount, nextIndex] = getTruthByte(buffer, nextIndex);
while (keyCount--) {
let key, value;
[key, nextIndex] = deserialize(buffer, nextIndex);
[value, nextIndex] = deserialize(buffer, nextIndex);
original[key] = value;
}
} else if (Array.from(type).toString() === GREEN.toString()) {
let length;
[length, nextIndex] = getTruthByte(buffer, nextIndex);
const characters = [];
for (let i = 0; i < length; i++) {
[character, nextIndex] = getTruthByte(buffer, nextIndex);
characters.push(character);
}
original = characters.map(v => String.fromCharCode(v)).join("");
}
return [original, nextIndex];
}
function getNextFourBytes(buffer, start) {
return [buffer.slice(start, start + 4), start + 4];
}
function getTruthByte(buffer, start) {
const [slice, nextIndex] = getNextFourBytes(buffer, start);
return [Array.from(slice).find(v => v != 0) || 0, nextIndex];
}
const obj = {hello: {nested: {nested: "world"}}};
const serialized = serialize(obj);
console.log(serialized);
const {Buffer} = require("buffer");
const serializedString = Buffer.from(serialized).toString("hex");
console.log(serializedString);
console.log(Buffer.from(serialized).toString("utf8"));
const buffer = parseFromString(serializedString);
const [deserialized] = deserialize(buffer);
console.log(deserialized);
<canvas id="canvas"></canvas>
<script>
// PS: You can run this code in Node.js too, just use this canvas polyfill - https://www.npmjs.com/package/canvas.
// What we need is an array of bytes, but I prefer working with the hex string
// because in real life, that's what you're more likely to get.
const serializedString = "ff0000ff010000ff00ff00ff000500ff000068ff006500ff6c0000ff6c0000ff006f00ffff0000ff000100ff00ff00ff000600ff00006eff000065ff007300ff007" +
"400ff000065ff000064ffff0000ff010000ff00ff00ff060000ff00006eff650000ff007300ff000074ff006500ff000064ff00ff00ff050000ff007700ff006f00" +
"ff000072ff00006cff000064ff";
const numberOfBytes = serializedString.length / 2;
// But this means we have to convert the hex byte stream to an array first!
const serializedIntArray = new Uint8Array(numberOfBytes);
for (let i = 0; i < numberOfBytes; i++) {
const currentByteAsHexString = serializedString[i * 2] + serializedString[(i * 2) + 1];
const currentByteAsInt = parseInt(currentByteAsHexString, 16);
serializedIntArray[i] = currentByteAsInt;
}
// Now we can draw the image
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const height = 100;
const imageData = ctx.createImageData(numberOfBytes/4, height);
for (let i = 0; i < numberOfBytes; i++) {
for (let h = 0; h < height; h++) {
imageData.data[i + numberOfBytes * h] = serializedIntArray[i];
}
}
ctx.putImageData(imageData, 0, 0);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment