できない.dev

UUIDを生成するには

ランダムな UUID(バージョン 4)を各言語で生成し、ハイフンなしの文字列にする方法と文字列から読み戻す方法を示す。
時刻順に並ぶバージョン 7 を作れる言語と、標準では作れない言語の違いも扱う。

公開:

選んだ言語は、ほかのサンプルコードを開いたときも最初に表示する。

Python

import uuid
 
u = uuid.uuid4()
print(u)             # 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
print(u.hex)         # 例: 3b1f8e0e6c0a4a439d0b7a9c2f1e5d44(ハイフンなしの 32 文字)
print(u.version)     # 4
 
print(uuid.UUID("3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44") == uuid.UUID("3B1F8E0E6C0A4A439D0B7A9C2F1E5D44"))  # True
 
print(uuid.uuid7())  # 例: 019a1b2c-3d4e-7f50-8a6b-9c0d1e2f3a4b(時刻順に並ぶ版。Python 3.14 以降)

uuid.uuid4() が乱数から UUID を作り、文字列にするとハイフン区切りの 36 文字、.hex にするとハイフンなしの 32 文字になる。
uuid.UUID(文字列) は大文字小文字とハイフンの有無を問わず読めるので、比較の前に整える必要はない。
uuid.uuid7() は Python 3.14 で追加された関数で、3.12 では AttributeError になる。

JavaScript

const { randomUUID } = require("node:crypto");
 
console.log(randomUUID());        // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
console.log(crypto.randomUUID()); // グローバルの crypto でも同じ(Node.js 19 以降)
 
const compact = randomUUID().replaceAll("-", "");
console.log(compact.length);      // 32(ハイフンなし)

Node.js では node:crypto の randomUUID が使え、グローバルの crypto.randomUUID() も同じ形式の値を返す。
ブラウザの crypto.randomUUID() は HTTPS か localhost のページに限られる。
ハイフンを外したいときは replaceAll で取り除く。

できないときReact で「Each child in a list should have a unique key prop」警告が消せない

TypeScript

import { randomUUID } from "node:crypto";
 
const id: string = randomUUID();
console.log(id); // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
 
// ブラウザでも動かすなら globalThis.crypto を使う
const browserId: string = globalThis.crypto.randomUUID();
 
// バージョン 4 の形式かどうか
const v4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
console.log(v4.test(id), v4.test(browserId)); // true true

node:crypto の randomUUID は文字列を返す。
ブラウザでも動かすなら globalThis.crypto.randomUUID() を使う。
バージョン 4 の UUID は、第 3 グループの先頭が 4、第 4 グループの先頭が 8・9・a・b のどれかになるので、正規表現で形式を確かめられる。

できないときReact で「Each child in a list should have a unique key prop」警告が消せない

Go

// go mod init example.com/uuid-sample && go get github.com/google/uuid
package main
 
import (
	"fmt"
 
	"github.com/google/uuid"
)
 
func main() {
	id := uuid.New()
	fmt.Println(id)           // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
	fmt.Println(int(id.Version())) // 4(そのまま出すと VERSION_4 と表示される)
 
	v7, err := uuid.NewV7() // 時刻順に並ぶ版
	if err != nil {
		panic(err)
	}
	fmt.Println(v7) // 例: 019a1b2c-3d4e-7f50-8a6b-9c0d1e2f3a4b
 
	parsed, err := uuid.Parse("3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44")
	fmt.Println(parsed, err) // 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44 <nil>
}

標準ライブラリに UUID は無く、github.com/google/uuid を go get で取り込む。
go get を忘れると no required module provides package で止まる。
uuid.New() はバージョン 4、uuid.NewV7() は時刻順に並ぶバージョン 7 を返す。
Version() の戻り値をそのまま出すと VERSION_4 と表示されるので、数字にするなら int に変換する。

できないときGo「cannot find module providing package」の原因と直し方

Rust

// Cargo.toml: uuid = { version = "1", features = ["v4", "v7"] }
use uuid::Uuid;
 
fn main() {
    let id = Uuid::new_v4();
    println!("{}", id);                 // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
    println!("{}", id.simple());        // 例: 3b1f8e0e6c0a4a439d0b7a9c2f1e5d44(ハイフンなし)
    println!("{}", id.get_version_num()); // 4
 
    println!("{}", Uuid::now_v7());     // 例: 019a1b2c-3d4e-7f50-8a6b-9c0d1e2f3a4b(時刻順に並ぶ版)
}

標準ライブラリに UUID は無いため、uuid クレートを使い、Cargo.toml で v4 と v7 の feature を有効にする。
feature を付けないと、Uuid::new_v4 が見つからないというコンパイルエラー(E0599)になる。
ハイフンなしの文字列は simple() で得る。

Java

import java.util.UUID;
 
public class Main {
    public static void main(String[] args) {
        UUID id = UUID.randomUUID();
        System.out.println(id);                          // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
        System.out.println(id.version());                // 4
        System.out.println(id.toString().replace("-", "")); // 例: 3b1f8e0e6c0a4a439d0b7a9c2f1e5d44
 
        UUID parsed = UUID.fromString("3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44");
        System.out.println(parsed); // 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
    }
}

java.util.UUID.randomUUID() が標準でバージョン 4 の UUID を返す。
ハイフンなしにするには toString().replace("-", "") とする。
UUID.fromString は不正な文字列を渡すと IllegalArgumentException になるので、外部から受け取った値を戻すときは例外を考慮する。

C#

using System;
 
var id = Guid.NewGuid();
Console.WriteLine(id);                // 例: 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44
Console.WriteLine(id.ToString("N"));  // 例: 3b1f8e0e6c0a4a439d0b7a9c2f1e5d44(ハイフンなし)
 
var parsed = Guid.Parse("3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44");
Console.WriteLine(parsed);            // 3b1f8e0e-6c0a-4a43-9d0b-7a9c2f1e5d44

.NET では Guid.NewGuid() がバージョン 4 の UUID を返す。
ToString("N") でハイフンなしの文字列になり、Guid.Parse で文字列から戻せる。
バージョン 7 を作る Guid.CreateVersion7 は .NET 8 には無く、コンパイルエラー(CS0117)になる。

バージョン 4 と 7 の違い

UUID のバージョンで、並び順の性質が変わる。

  • バージョン 4 は 122 ビットが乱数で、並び順に意味が無い。
  • バージョン 7 は先頭に作成時刻(ミリ秒)を持つので、作った順に文字列として昇順で並ぶ。

データベースの主キーにバージョン 4 を使うとインデックスの途中に挿入されるため、挿入順に並ぶバージョン 7 のほうが向く場面がある。
Python 3.14 の uuid.uuid7() で 5,000 個を続けて作ったところ、作った順がそのまま昇順で、重複は無かった。

標準の関数だけで作れるのは Python 3.14 以降で、Go と Rust は外部のパッケージ(github.com/google/uuid と uuid クレートの v7 feature)が要る。
JavaScript の crypto.randomUUID() と Java の UUID.randomUUID() はバージョン 4 だけを作り、C# は .NET 8 では作れない。

形式を確かめる

バージョン 4 の UUID は xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx の形で、y は 8・9・a・b のどれかになる。
Python で uuid.uuid4() を 20,000 個、Node.js で crypto.randomUUID() を 20,000 個作って正規表現で確かめたところ、すべてこの形に一致し、重複は無かった。
Python の uuid.uuid7() が作る値は、バージョンを表す位置が 7 になる。

乱数で自作しない・React の key には毎回作らない

Math.random() などで UUID に見える文字列を組み立てる自作は、重複しない保証が無く、予測もされやすい。
OS の乱数を使う crypto.randomUUID や uuid.uuid4 を使う。
React のリストの key に、描画のたびに crypto.randomUUID() を呼ぶのも避ける。
描画のたびに別の値になり、要素が毎回作り直される。
ID はデータを作るときに一度だけ生成して、データに持たせておく。