Open5

UUIDのいい縮め方

Nakano as a ServiceNakano as a Service

uuidは通常36文字、ハイフンを抜けば32文字。

base58でエンコードすると最小19から最大22文字に収まる。またデコード処理をかけることで元のUUIDに戻すこともできる。

base58はbase64から+/などの特殊文字を取り除いたエンコード形式で、ビットコインアドレスなどで一般的に使われている。

https://ja.wikipedia.org/wiki/Base58

簡単な実装は以下の通り

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
}
Nakano as a ServiceNakano as a Service

長さを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)
Nakano as a ServiceNakano as a Service
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パターン ✅️なし 文字セットと文字長が可変であるゆえにルールが必要
速度 最も速い エンコード処理が遅い 速い 速い
実装の単純さ 標準ライブラリで実装 比較的簡単 簡単 簡単