Last active
January 20, 2022 14:08
-
-
Save shalvah/46573b7c18b772ac6b4fe1bd7da3d878 to your computer and use it in GitHub Desktop.
Custom serialization format https://blog.shalvah.me/posts/serialization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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