Open5
UUIDのいい縮め方
uuidは通常36文字、ハイフンを抜けば32文字。
base58でエンコードすると最小19から最大22文字に収まる。またデコード処理をかけることで元のUUIDに戻すこともできる。
base58はbase64から+
や/
などの特殊文字を取り除いたエンコード形式で、ビットコインアドレスなどで一般的に使われている。
簡単な実装は以下の通り
const BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
const BASE = BigInt(BASE58.length)
function uuid58() {
const uuid = crypto.randomUUID().replace(/-/g, "")
let b = BigInt(`0x${uuid}`)
let u58 = ""
do {
u58 = BASE58[Number(b % BASE)] + u58
b = b / BASE
} while (b > 0)
return u58
}
firestoreのIDは結構雑に発番しており、20文字に収まる。
長さを22文字に揃えるパディング処理を追加し、デコード処理も実装したバージョン。
// Base58で使用するアルファベット(Bitcoin標準)
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
const BASE = BigInt(ALPHABET.length)
/**
* encodeUUID
* 標準形式のUUID文字列(例: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")を
* Base58エンコードされた22文字の文字列に変換します。
*
* @param uuid 標準形式のUUID文字列
* @returns 固定長22文字のBase58エンコード文字列
*/
export function encodeUUID(uuid: string): string {
// ハイフンを除去して32桁の16進数文字列に変換
const hex = uuid.replace(/-/g, "")
// 16進数文字列をBigIntに変換
const num = BigInt("0x" + hex)
// BigIntをBase58文字列に変換
let result = ""
let n = num
while (n > 0) {
const mod = n % BASE
result = ALPHABET[Number(mod)] + result
n = n / BASE
}
// 固定長22文字にするため、足りない場合は左側に '1'(ALPHABETの先頭文字)でパディング
while (result.length < 22) {
result = ALPHABET[0] + result
}
return result
}
/**
* generateBase58UUID
* crypto.randomUUID() を用いて新規UUIDを生成し、
* Base58エンコードされた22文字の文字列として返します。
*
* @returns 固定長22文字のBase58エンコードされたUUID
*/
export function generateBase58UUID(): string {
const uuid = crypto.randomUUID()
return encodeUUID(uuid)
}
/**
* decodeBase58UUID
* 22文字のBase58エンコードされたUUID文字列を、標準形式のUUID(ハイフン付き)にデコードします。
*
* @param b58 22文字のBase58エンコードされたUUID文字列
* @returns 標準形式のUUID文字列 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
*/
export function decodeBase58UUID(b58: string): string {
if (b58.length !== 22) {
throw new Error("Invalid base58 UUID length")
}
// Base58文字列をBigIntに変換
let num = 0n
for (const char of b58) {
const index = ALPHABET.indexOf(char)
if (index === -1) {
throw new Error(`Invalid character "${char}" in base58 UUID`)
}
num = num * BASE + BigInt(index)
}
// BigIntを16進数文字列に変換(UUIDは16バイト=32桁の16進数)
const hex = num.toString(16).padStart(32, "0")
// ハイフンを挿入して標準形式に整形
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}
// --- 使用例 ---
// 1. 新規にBase58エンコードされたUUIDを生成する場合
const b58uuid = generateBase58UUID()
console.log("Base58 UUID:", b58uuid)
// 2. 既存の標準形式UUIDをBase58エンコードする場合
const sampleUUID = "123e4567-e89b-12d3-a456-426614174000"
const encoded = encodeUUID(sampleUUID)
console.log("Encoded sample UUID:", encoded)
// 3. Base58エンコードされたUUIDを元のUUID形式にデコードする場合
const decodedUUID = decodeBase58UUID(b58uuid)
console.log("Decoded UUID:", decodedUUID)
uuid | uuid58 | autoId | nanoId | |
---|---|---|---|---|
実装 | https://ja.wikipedia.org/wiki/UUID | https://zenn.dev/link/comments/4e5a4e64ee7a07 | https://github.com/googleapis/nodejs-firestore/blob/1e714a8b7952b65872e65533cfe74d303dfabe20/dev/src/util.ts#L57 | https://zelark.github.io/nano-id-cc/ |
UUID形式にできる | ✅️できる | ✅️できる | できない | できない |
依存ライブラリ | ✅️なし | ✅️なし | firestoreに依存 | nanoidに依存 |
長さ | 36字 ハイフン抜きで32字 | ✅️19〜22字で固定長にもできる | ✅️20字 | ✅️可変長でデフォルト21字 |
長さのランダム性 | ✅️なし | ありだがパディングで無くせる | ✅️なし | ✅️なし |
デフォルト文字セット | 16進数とハイフン | 英数字からハイフン・アンダーバー・紛らわしい文字を除いたもの123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz | ✅️英数字(ハイフン・アンダーバーなし)ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 | 英数字0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz- |
文字セットのバリエーションのブレ | ✅️なし | 3つのスタイルがある | ✅️なし | 無限大 |
ダブルクリックで選択 | できない | ✅️できる | ✅️できる | デフォルトではできないが設定可能 |
紛らわしい文字 | ✅️なし | ✅️なし | あり | デフォルトではありだが除外可能 |
文字セットの変更 | 不可能 | ✅️可能 | 不可能 | ✅️可能 |
✅️最も一般的 | 一般的でない | ✅️Firestoreでは一般的 | 一般的でない | |
バリエーション | ✅️なし | 一般的には3つの文字セットとパディングの有無の6パターン | ✅️なし | 文字セットと文字長が可変であるゆえにルールが必要 |
速度 | 最も速い | エンコード処理が遅い | 速い | 速い |
実装の単純さ | 標準ライブラリで実装 | 比較的簡単 | 簡単 | 簡単 |
uuidをbase58で縮めるライブラリを作った