Created
October 24, 2024 14:36
-
-
Save travishorn/54a63cd75ebca271c185bda0dc46543b to your computer and use it in GitHub Desktop.
An example of building custom encoding/decoding functions to pack (well-defined and well-formed) data into smaller space.
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
// Use the `Buffer` class. Requires Node.js. | |
import { Buffer } from "node:buffer"; | |
/** | |
* @typedef CommodityPrice | |
* @property {string} id - A unique identifier for this piece of information | |
* @property {string} commodityId - The identifier for the commodity | |
* @property {Date} postedAt - When this price was posted | |
* @property {number} price - The price of the commodity when posted | |
*/ | |
/** | |
* Validate commodity price information to ensure it conforms to the format required by the encoding function. | |
* | |
* @param {CommodityPrice} commodityPriceObject - A JavaScript object containing commodity price information | |
* | |
* @throws {Error} If `id` is not a valid UUID. | |
* @throws {Error} If `commodityId` is not a valid UUID. | |
* @throws {Error} If `postedAt` is not a valid `Date` object. | |
* @throws {Error} If `price` is not a valid number. | |
*/ | |
function validateCommodityPriceObject(commodityPriceObject) { | |
if (!/^[0-9a-fA-F-]{36}$/.test(commodityPriceObject.id)) { | |
throw new Error("Invalid UUID format for id"); | |
} | |
if (!/^[0-9a-fA-F-]{36}$/.test(commodityPriceObject.commodityId)) { | |
throw new Error("Invalid UUID format for commodityId"); | |
} | |
if (!(commodityPriceObject.postedAt instanceof Date)) { | |
throw new Error("postedAt must be a valid Date object"); | |
} | |
if (isNaN(commodityPriceObject.price)) { | |
throw new Error("Price must be a number"); | |
} | |
} | |
/** | |
* Encode commodity price information from a JavaScript object into a custom buffer. | |
* | |
* @param {CommodityPrice} commodityPriceObject - A JavaScript object containing commodity price information | |
* @returns {Buffer} A custom buffer containing commodity price information | |
*/ | |
function encodeCommodityPrice(commodityPriceObject) { | |
validateCommodityPriceObject(commodityPriceObject); | |
const buffer = Buffer.alloc(48); | |
buffer.write(commodityPriceObject.id.replace(/-/g, ""), 0, 16, "hex"); | |
buffer.write( | |
commodityPriceObject.commodityId.replace(/-/g, ""), | |
16, | |
16, | |
"hex" | |
); | |
buffer.writeDoubleLE(commodityPriceObject.postedAt.getTime(), 32); | |
buffer.writeDoubleLE(commodityPriceObject.price, 40); | |
return buffer; | |
} | |
/** | |
* Decode commodity price information from a custom buffer to a JavaScript object. | |
* | |
* @param {Buffer} commodityPriceBuffer - A custom buffer containing commodity price information | |
* @returns {CommodityPrice} A JavaScript object containing commodity price information | |
*/ | |
function decodeCommodityPrice(commodityPriceBuffer) { | |
if (commodityPriceBuffer.length !== 48) { | |
throw new Error("Invalid buffer length"); | |
} | |
const id = commodityPriceBuffer | |
.toString("hex", 0, 16) | |
.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); | |
const commodityId = commodityPriceBuffer | |
.toString("hex", 16, 32) | |
.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); | |
const postedAt = new Date(commodityPriceBuffer.readDoubleLE(32)); | |
const price = commodityPriceBuffer.readDoubleLE(40); | |
return { id, commodityId, postedAt, price }; | |
} | |
// EXAMPLE USAGE | |
// Create a JavaScript object containing commodity price information | |
/** @type {CommodityPrice} */ | |
const commodityPriceObject = { | |
id: "7acb9d5e-b83c-4537-893f-389ca70f67c1", | |
commodityId: "5e5532f0-b6ca-49bc-a9e4-8354814a9be0", | |
postedAt: new Date(), | |
price: 1234, | |
}; | |
console.log("Original object:", commodityPriceObject); | |
console.log( | |
"Byte length:", | |
Buffer.byteLength(JSON.stringify(commodityPriceObject), "utf8"), | |
"\n\n" | |
); | |
// The object's size is 149 bytes | |
// Encode the object into a buffer | |
const commodityPriceBuffer = encodeCommodityPrice(commodityPriceObject); | |
console.log("Encoded buffer:", commodityPriceBuffer); | |
console.log("Byte length:", commodityPriceBuffer.byteLength, "\n"); | |
// The buffer's size is 48 bytes. That's a 3.1:1 compression ratio. A 67.8% reduction in size. | |
// Decode the buffer back into an object | |
const decodedCommodityPrice = decodeCommodityPrice(commodityPriceBuffer); | |
console.log("Decoded object:", decodedCommodityPrice); | |
console.log( | |
"Byte length:", | |
Buffer.byteLength(JSON.stringify(decodedCommodityPrice), "utf8"), | |
"\n\n" | |
); | |
// The object is back to 149 bytes |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment