業務改善に取り組んでいると本番データではなく検証データが欲しいときが多々あると思うんですが、毎回本番をもじって作るのが面倒でした。
今回、CopilotにJavaScriptをHTMLに埋め込んだジェネレータを作って欲しいと頼んだら、ほとんど一瞬で作ってくれて、ちゃんと動作してくれて、感動したっていうお話。
画面はこんな感じ

HTML+JavaScriptのソースはこちら
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>架空個人データ ジェネレーター(オフライン)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{--fg:#222;--bg:#fff;--muted:#666;--pri:#2563eb;--bdr:#ddd}
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"Noto Sans JP","Apple Color Emoji","Segoe UI Emoji";color:var(--fg);background:var(--bg);margin:0}
header{padding:18px 20px;border-bottom:1px solid var(--bdr)}
header h1{font-size:18px;margin:0 0 4px}
header p{margin:0;color:var(--muted);font-size:13px}
main{max-width:1100px;margin:20px auto;padding:0 16px 40px}
.panel{border:1px solid var(--bdr);border-radius:8px;padding:14px;margin-bottom:16px}
.row{display:flex;flex-wrap:wrap;gap:12px}
.row > div{flex:1 1 220px;min-width:220px}
label{font-size:14px}
input[type="number"],input[type="text"]{width:100%;padding:8px;border:1px solid var(--bdr);border-radius:6px;font-size:14px}
fieldset{border:none;padding:0;margin:0}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-top:8px}
.btns{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
button{background:var(--pri);color:#fff;border:none;border-radius:8px;padding:9px 14px;font-weight:600;cursor:pointer}
button.secondary{background:#f3f4f6;color:#111;border:1px solid var(--bdr)}
button:disabled{opacity:.6;cursor:not-allowed}
.note{color:var(--muted);font-size:12px;margin-top:6px}
table{width:100%;border-collapse:collapse;margin-top:10px;font-size:13px}
th,td{border-bottom:1px solid #eee;padding:8px 6px;text-align:left;vertical-align:top}
thead th{position:sticky;top:0;background:#fafafa}
.summary{font-size:13px;color:var(--muted);margin:6px 0}
.danger{color:#b91c1c}
.ok{color:#047857}
.footer{font-size:12px;color:var(--muted);margin-top:12px}
.right{margin-left:auto}
@media (max-width:640px){table{display:block;overflow:auto}}
</style>
</head>
<body>
<header>
<h1>架空個人データ ジェネレーター(HTML + JavaScript)</h1>
<p>選択した項目で1~1000件のテストデータを生成。すべて架空データです。</p>
</header>
<main>
<section class="panel">
<div class="row">
<div>
<label for="count">データ件数(1~1000)</label>
<input id="count" type="number" min="1" max="1000" step="1" value="100" />
</div>
<div>
<label for="seed">乱数シード(任意:同じシードで再現可)</label>
<input id="seed" type="text" placeholder="例: 2025-11-01 or abcd1234" />
</div>
</div>
<fieldset style="margin-top:12px">
<legend style="font-weight:700;margin-bottom:6px">項目を選択</legend>
<div class="grid">
<label><input type="checkbox" class="field" value="id" checked> ID</label>
<label><input type="checkbox" class="field" value="employeeCode" checked> 社員コード(10桁)</label>
<label><input type="checkbox" class="field" value="taskId" checked> タスクID(T+6桁)</label>
<label><input type="checkbox" class="field" value="date" checked> 日付(yyyy/MM/dd)</label>
<label><input type="checkbox" class="field" value="priority" checked> 優先順位(高/中/低)</label>
<label><input type="checkbox" class="field" value="name" checked> 氏名</label>
<label><input type="checkbox" class="field" value="kana" checked> フリガナ</label>
<label><input type="checkbox" class="field" value="gender" checked> 性別</label>
<label><input type="checkbox" class="field" value="birthday" checked> 生年月日</label>
<label><input type="checkbox" class="field" value="age" checked> 年齢</label>
<label><input type="checkbox" class="field" value="email" checked> メール</label>
<label><input type="checkbox" class="field" value="mobile" checked> 携帯電話</label>
<label><input type="checkbox" class="field" value="phone"> 固定電話</label>
<label><input type="checkbox" class="field" value="zip" checked> 郵便番号</label>
<label><input type="checkbox" class="field" value="pref" checked> 都道府県</label>
<label><input type="checkbox" class="field" value="city" checked> 市区町村</label>
<label><input type="checkbox" class="field" value="address" checked> 住所(番地)</label>
</div>
</fieldset>
<div class="btns">
<button id="genBtn">生成</button>
<button id="clearBtn" class="secondary">クリア</button>
<span class="note">※ 生成はすべてブラウザ内で完結し、サーバー送信は行いません。</span>
</div>
</section>
<section class="panel">
<div class="btns">
<button id="dlCsv">CSVダウンロード</button>
<button id="dlJson" class="secondary">JSONダウンロード</button>
<button id="copyBtn" class="secondary">コピー(CSV)</button>
<span class="right summary" id="summary">未生成</span>
</div>
<div id="warn" class="summary danger" style="display:none"></div>
<div id="ok" class="summary ok" style="display:none"></div>
<div id="tableWrap"></div>
<div class="footer">※ CSVはUTF-8(BOM付き)。Excelでも文字化けしにくくしています。</div>
</section>
</main>
<script>
/* ====== 調整しやすい定数(「日付」の範囲など) ====== */
const RANDOM_DATE_MIN = new Date(Date.UTC(1990, 0, 1)); // 1990-01-01
const RANDOM_DATE_MAX = new Date(Date.UTC(2035, 11, 31)); // 2035-12-31
/* ===== 小さなユーティリティ(シード付き乱数、日付、IDなど) ===== */
// 文字列→32bitハッシュ
function hash32(str){
let h = 2166136261 >>> 0;
for (let i=0;i<str.length;i++){
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
// Mulberry32: 軽量なシード乱数
function mulberry32(seed){
let t = seed >>> 0;
return function(){
t += 0x6D2B79F5;
let r = Math.imul(t ^ (t >>> 15), 1 | t);
r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
}
}
function choice(rng, arr){ return arr[Math.floor(rng()*arr.length)]; }
function intN(rng, min, max){ // [min, max]
return Math.floor(rng()*(max-min+1)) + min;
}
function pad(n, len){ return String(n).padStart(len,'0'); }
function randDate(rng, minY, maxY){
const y = intN(rng, minY, maxY);
const m = intN(rng, 1, 12);
const lastDay = new Date(y, m, 0).getDate();
const d = intN(rng, 1, lastDay);
const dt = new Date(Date.UTC(y, m-1, d));
return dt;
}
// 任意範囲からランダム日付
function randDateBetween(rng, minDate, maxDate){
const minMs = minDate.getTime();
const maxMs = maxDate.getTime();
const t = minMs + Math.floor(rng() * (maxMs - minMs + 1));
return new Date(t);
}
function toISODate(d){
return d.getUTCFullYear()+'-'+pad(d.getUTCMonth()+1,2)+'-'+pad(d.getUTCDate(),2);
}
// yyyy/MM/dd
function toSlashDate(d){
const y = d.getUTCFullYear();
const m = pad(d.getUTCMonth()+1, 2);
const day = pad(d.getUTCDate(), 2);
return `${y}/${m}/${day}`;
}
function calcAge(birth){
const today = new Date();
let age = today.getFullYear() - birth.getUTCFullYear();
const m = today.getMonth() + 1;
const bdM = birth.getUTCMonth()+1;
const bdD = birth.getUTCDate();
if (m < bdM || (m===bdM && today.getDate() < bdD)) age--;
return age;
}
// UUID v4(簡易)
function uuidv4(rng){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c=>{
const r = Math.floor(rng()*16);
const v = (c==='x') ? r : (r&0x3|0x8);
return v.toString(16);
});
}
/* ===== データ候補(日本語名・かな・ローマ字をペアで最小限) ===== */
const SURNAMES = [
{kanji:'佐藤',kana:'サトウ',romaji:'sato'},
{kanji:'鈴木',kana:'スズキ',romaji:'suzuki'},
{kanji:'高橋',kana:'タカハシ',romaji:'takahashi'},
{kanji:'田中',kana:'タナカ',romaji:'tanaka'},
{kanji:'伊藤',kana:'イトウ',romaji:'ito'},
{kanji:'山本',kana:'ヤマモト',romaji:'yamamoto'},
{kanji:'中村',kana:'ナカムラ',romaji:'nakamura'},
{kanji:'小林',kana:'コバヤシ',romaji:'kobayashi'},
{kanji:'加藤',kana:'カトウ',romaji:'kato'},
{kanji:'吉田',kana:'ヨシダ',romaji:'yoshida'}
];
const GIVEN_MALE = [
{kanji:'太郎',kana:'タロウ',romaji:'taro'},
{kanji:'翔',kana:'ショウ',romaji:'sho'},
{kanji:'蓮',kana:'レン',romaji:'ren'},
{kanji:'陽斗',kana:'ハルト',romaji:'haruto'},
{kanji:'颯太',kana:'ソウタ',romaji:'sota'},
{kanji:'大和',kana:'ヤマト',romaji:'yamato'},
{kanji:'蒼',kana:'アオイ',romaji:'aoi'},
{kanji:'悠真',kana:'ユウマ',romaji:'yuma'},
{kanji:'陸',kana:'リク',romaji:'riku'},
{kanji:'湊',kana:'ミナト',romaji:'minato'}
];
const GIVEN_FEMALE = [
{kanji:'花子',kana:'ハナコ',romaji:'hanako'},
{kanji:'葵',kana:'アオイ',romaji:'aoi'},
{kanji:'結衣',kana:'ユイ',romaji:'yui'},
{kanji:'陽菜',kana:'ヒナ',romaji:'hina'},
{kanji:'さくら',kana:'サクラ',romaji:'sakura'},
{kanji:'美咲',kana:'ミサキ',romaji:'misaki'},
{kanji:'凛',kana:'リン',romaji:'rin'},
{kanji:'楓',kana:'カエデ',romaji:'kaede'},
{kanji:'愛',kana:'アイ',romaji:'ai'},
{kanji:'結菜',kana:'ユイナ',romaji:'yuina'}
];
const PREFS = [
'北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県',
'茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県',
'新潟県','富山県','石川県','福井県','山梨県','長野県',
'岐阜県','静岡県','愛知県','三重県',
'滋賀県','京都府','大阪府','兵庫県','奈良県','和歌山県',
'鳥取県','島根県','岡山県','広島県','山口県',
'徳島県','香川県','愛媛県','高知県',
'福岡県','佐賀県','長崎県','熊本県','大分県','宮崎県','鹿児島県','沖縄県'
];
const CITY_SUFFIX = ['市','区','町','村'];
const CITY_BASE = ['中央','北','南','東','西','旭','桜','若葉','美浜','青葉','緑','千代田','港','大和','藤','川','山田','高崎','堺','東雲','新都','朝日'];
const TOWN_SUFFIX = ['町','丁目','ヶ丘','台','の里','原','新田','本町','松原'];
/* ===== 擬似データ生成器 ===== */
function genMobile(rng){
const head = choice(rng, ['090','080','070']);
return `${head}-${pad(intN(rng,0,9999),4)}-${pad(intN(rng,0,9999),4)}`;
}
function genPhone(rng){
const head = choice(rng, ['03','045','06','052','075','082','092','011']);
return `${head}-${pad(intN(rng,100,9999),4)}-${pad(intN(rng,0,9999),4)}`;
}
function genZip(rng){
return `${pad(intN(rng,0,999),3)}-${pad(intN(rng,0,9999),4)}`;
}
function genAddress(rng){
const pref = choice(rng, PREFS);
const city = choice(rng, CITY_BASE) + choice(rng, CITY_SUFFIX);
const town = choice(rng, CITY_BASE) + choice(rng, TOWN_SUFFIX);
const chome = intN(rng,1,6);
const ban = intN(rng,1,20);
const go = intN(rng,1,30);
const addr = `${town}${chome}丁目${ban}-${go}`;
return {pref, city, address: addr};
}
function genName(rng){
const gender = rng() < 0.5 ? '男' : '女';
const sur = choice(rng, SURNAMES);
const giv = gender === '男' ? choice(rng, GIVEN_MALE) : choice(rng, GIVEN_FEMALE);
const name = `${sur.kanji} ${giv.kanji}`;
const kana = `${sur.kana} ${giv.kana}`;
const romaji = `${giv.romaji}.${sur.romaji}`;
return {gender, name, kana, romaji};
}
function genEmail(rng, romaji){
const num = pad(intN(rng,0,99),2);
const domain = choice(rng, ['example.jp','sample.co.jp','test.local','mail.example']);
return `${romaji.replace(/[^a-z.]/g,'')}${num}@${domain}`.toLowerCase();
}
/* === 新規: 社員コード(10桁数値) === */
function genEmployeeCode(rng){
let s = '';
for (let i=0;i<10;i++){ s += intN(rng,0,9); }
return s;
}
/* === 新規: タスクID(T + 6桁) === */
function genTaskId(rng){
return 'T' + pad(intN(rng,0,999999), 6);
}
/* === 新規: 過去~未来のランダム日付(yyyy/MM/dd)=== */
function genRandomDateStr(rng){
const d = randDateBetween(rng, RANDOM_DATE_MIN, RANDOM_DATE_MAX);
// UTC基準で整形(時差の影響を避ける)
const utc = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
return toSlashDate(utc);
}
/* === 新規: 優先順位 === */
function genPriority(rng){
return choice(rng, ['高','中','低']);
}
function buildRecord(rng){
const id = uuidv4(rng);
const person = genName(rng);
const bd = randDate(rng, 1955, 2015);
const age = calcAge(bd);
const mobile = genMobile(rng);
const phone = genPhone(rng);
const zip = genZip(rng);
const addr = genAddress(rng);
const email = genEmail(rng, person.romaji);
// 新規項目の生成
const employeeCode = genEmployeeCode(rng);
const taskId = genTaskId(rng);
const date = genRandomDateStr(rng);
const priority = genPriority(rng);
return {
id,
employeeCode, taskId, date, priority,
name: person.name, kana: person.kana, gender: person.gender,
birthday: toISODate(bd), age,
email, mobile, phone,
zip, pref: addr.pref, city: addr.city, address: addr.address
};
}
/* ===== CSV / JSON 出力 ===== */
function toCSV(rows, headers){
const escape = (v)=>{
if (v === null || v === undefined) return '';
const s = String(v);
if (/[",\n]/.test(s)) return '"' + s.replace(/"/g,'""') + '"';
return s;
};
const head = headers.join(',');
const body = rows.map(r => headers.map(h=>escape(r[h])).join(',')).join('\n');
return head + '\n' + body;
}
function downloadText(filename, text, withBOM=false){
const blob = new Blob([withBOM ? '\uFEFF' + text : text], {type:'text/plain;charset=utf-8'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
}
/* ===== UI ロジック ===== */
let DATA = [];
let HEADERS = [];
function getSelectedFields(){
const checks = Array.from(document.querySelectorAll('.field:checked')).map(el=>el.value);
return checks;
}
function renderTable(rows, headers){
const wrap = document.getElementById('tableWrap');
if (!rows.length){ wrap.innerHTML = ''; return; }
const thead = `<thead><tr>${headers.map(h=>`<th>${h}</th>`).join('')}</tr></thead>`;
const tbody = `<tbody>` + rows.map(r=>{
return `<tr>` + headers.map(h=>`<td>${r[h] ?? ''}</td>`).join('') + `</tr>`;
}).join('') + `</tbody>`;
wrap.innerHTML = `<table>${thead}${tbody}</table>`;
}
function showMsg(id, text){
const el = document.getElementById(id);
el.style.display = text ? '' : 'none';
el.textContent = text || '';
}
document.getElementById('genBtn').addEventListener('click', ()=>{
showMsg('warn',''); showMsg('ok','');
const countEl = document.getElementById('count');
const seedStr = (document.getElementById('seed').value || '').trim();
let n = Number(countEl.value);
if (!Number.isInteger(n) || n < 1 || n > 1000){
showMsg('warn','件数は1~1000の整数で指定してください。');
return;
}
const fields = getSelectedFields();
if (!fields.length){
showMsg('warn','少なくとも1つの項目を選択してください。');
return;
}
// シード決定
const seed = seedStr ? hash32(seedStr) : hash32(String(Date.now()));
const rng = mulberry32(seed);
// 生成
const rows = [];
for (let i=0; i<n; i++){
rows.push(buildRecord(rng));
}
// 列の順序(選択順に合わせる)
const headers = fields;
DATA = rows.map(r=>{
const o = {};
headers.forEach(h=>{ o[h] = r[h]; });
return o;
});
HEADERS = headers;
renderTable(DATA, HEADERS);
document.getElementById('summary').textContent = `生成件数: ${DATA.length}件 / 項目数: ${HEADERS.length}(シード: ${seedStr || '自動'})`;
showMsg('ok', '生成が完了しました。');
});
document.getElementById('clearBtn').addEventListener('click', ()=>{
DATA = []; HEADERS = [];
renderTable([], []);
document.getElementById('summary').textContent = '未生成';
showMsg('warn',''); showMsg('ok','');
});
document.getElementById('dlCsv').addEventListener('click', ()=>{
if (!DATA.length){ showMsg('warn','先にデータを生成してください。'); return; }
const csv = toCSV(DATA, HEADERS);
downloadText('testdata.csv', csv, true); // BOM付き
});
document.getElementById('dlJson').addEventListener('click', ()=>{
if (!DATA.length){ showMsg('warn','先にデータを生成してください。'); return; }
const json = JSON.stringify(DATA, null, 2);
downloadText('testdata.json', json, false);
});
document.getElementById('copyBtn').addEventListener('click', async ()=>{
if (!DATA.length){ showMsg('warn','先にデータを生成してください。'); return; }
const csv = toCSV(DATA, HEADERS);
try{
await navigator.clipboard.writeText(csv);
showMsg('ok','CSVをクリップボードへコピーしました。');
}catch(e){
showMsg('warn','コピーに失敗しました。ダウンロードをご利用ください。');
}
});
</script>
</body>
</html>このままメモ帳に貼り付けて、拡張子をHTMLに変更するだけで動く。
細かい項目の調整はソースを改めてCopilotに投げるか、皆さんの力でちょちょいとソースを変更すれば何とかなる。
