Created
March 23, 2026 14:17
-
-
Save kobitoDevelopment/ed32d587eedaf67108b717da5c992d76 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| <a id="download-csv" download="data.csv">CSVダウンロード</a> |
This file contains hidden or 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
| /** | |
| * @file JSONデータをCSVに変換してダウンロードする機能を提供するスクリプト | |
| * | |
| * 処理の流れ: | |
| * 1. DOMContentLoaded発火後、initApp()がconvertJsonToCsv(SAMPLE_DATA)を呼び出してCSV変換を開始する | |
| * 2. convertJsonToCsv()内部でextractHeaders()を呼び出し、SAMPLE_DATAの全オブジェクトを走査して全キーを抽出する | |
| * 3. 抽出したキー配列をヘッダー行として、各キーにescapeCsvField()を適用してカンマ区切り文字列にする | |
| * 4. convertToCsvRows()を呼び出し、各オブジェクトの値をヘッダーの列順序に従って取り出す | |
| * 5. convertToCsvRows()内部で各フィールドにescapeCsvField()を適用し、ダブルクォート・カンマ・改行を含む値をRFC 4180準拠でエスケープする | |
| * 6. ヘッダー行とデータ行をCRLF(\r\n)で結合し、CSV文字列として返却する | |
| * 7. downloadCsv()がCSV文字列の先頭にBOM(0xEF 0xBB 0xBF)を付与し、MIMEタイプ"text/csv"のBlobを生成する。このBlobがブラウザ上で.csvファイルとして扱えるオブジェクトとなる | |
| * 8. URL.createObjectURL()でBlobのURLを生成し、HTMLに配置済みのaタグ(#download-csv)のhrefに設定する | |
| * 9. ユーザーがaタグをクリックすると、ブラウザのデフォルト動作(download属性)によりCSVファイルがダウンロードされる | |
| */ | |
| /** @type {ReadonlyArray<{id: number, name: string, email: string, age: number, department: string}>} */ | |
| const SAMPLE_DATA = [ | |
| { id: 1, name: "田中太郎", email: "tanaka@example.com", age: 30, department: "開発部" }, | |
| { id: 2, name: "佐藤花子", email: "sato@example.com", age: 25, department: "営業部" }, | |
| { id: 3, name: "鈴木一郎", email: "suzuki@example.com", age: 35, department: "人事部" }, | |
| { id: 4, name: '山田"次郎"', email: "yamada@example.com", age: 28, department: "開発部" }, | |
| { id: 5, name: "高橋美咲", email: "takahashi@example.com", age: 32, department: "営業部" }, | |
| ]; | |
| /** | |
| * CSVのヘッダー行を生成するために、オブジェクト配列から全キーを抽出する。 | |
| * オブジェクトごとにキーの有無が異なる場合でも、全行を網羅したヘッダーを得るために全オブジェクトを走査する。 | |
| * @param {Array<Record<string, unknown>>} objects - オブジェクト配列 | |
| * @returns {string[]} キー名の配列 | |
| */ | |
| const extractHeaders = (objects) => { | |
| const headerMap = {}; | |
| objects.forEach((obj) => { | |
| Object.keys(obj).forEach((key) => { | |
| headerMap[key] = true; | |
| }); | |
| }); | |
| return Object.keys(headerMap); | |
| }; | |
| /** | |
| * CSV出力時にフィールド値が区切り文字やクォートを含むとパースが壊れるため、 | |
| * RFC 4180に準拠したエスケープ処理を適用する。 | |
| * @param {unknown} field - エスケープ対象の値 | |
| * @returns {string} エスケープ済みの文字列 | |
| */ | |
| const escapeCsvField = (field) => { | |
| const str = field == null ? "" : String(field); | |
| if (str.includes('"') || str.includes(",") || str.includes("\n") || str.includes("\r")) { | |
| return `"${str.replace(/"/g, '""')}"`; | |
| } | |
| return str; | |
| }; | |
| /** | |
| * ヘッダーの列順序に従って各オブジェクトの値をCSV行文字列に変換する。 | |
| * ヘッダーと列順序を一致させることで、CSVとしての整合性を保証する。 | |
| * @param {Array<Record<string, unknown>>} objects - オブジェクト配列 | |
| * @param {string[]} headers - ヘッダー(キー名)の配列 | |
| * @returns {string[]} CSV行の配列(ヘッダー行を含まない) | |
| */ | |
| const convertToCsvRows = (objects, headers) => | |
| objects.map((obj) => headers.map((header) => escapeCsvField(obj[header])).join(",")); | |
| /** | |
| * ヘッダー抽出・行変換・エスケープの各処理を統合し、 | |
| * オブジェクト配列からダウンロード可能なCSV文字列を生成する。 | |
| * @param {Array<Record<string, unknown>>} data - 変換対象のオブジェクト配列 | |
| * @returns {string} CSV文字列 | |
| */ | |
| const convertJsonToCsv = (data) => { | |
| const headers = extractHeaders(data); | |
| const headerRow = headers.map(escapeCsvField).join(","); | |
| const bodyRows = convertToCsvRows(data, headers); | |
| return [headerRow, ...bodyRows].join("\r\n"); | |
| }; | |
| /** | |
| * ブラウザにはCSV文字列を直接保存するAPIがないため、 | |
| * BOM付きUTF-8のBlobを生成し、aタグのhrefに設定してダウンロードを実行する。 | |
| * BOMを付与するのはExcelで開いた際の文字化けを防ぐため。 | |
| * @param {HTMLAnchorElement} anchor - ダウンロード用のaタグ要素 | |
| * @param {string} csvString - ダウンロード対象のCSV文字列 | |
| * @returns {void} | |
| */ | |
| const downloadCsv = (anchor, csvString) => { | |
| const bom = new Uint8Array([0xef, 0xbb, 0xbf]); | |
| const blob = new Blob([bom, csvString], { type: "text/csv;charset=utf-8" }); | |
| // BlobURLはページ遷移・タブ閉じ時にGCされるため、非SPAでは手動revokeは不要。 | |
| // SPAの場合はページ遷移前にURL.revokeObjectURL()で解放すること。 | |
| anchor.href = URL.createObjectURL(blob); | |
| }; | |
| /** | |
| * DOMContentLoaded後にダウンロードボタンへイベントリスナーを登録する。 | |
| * DOM構築完了後に実行することで、要素の取得失敗を防ぐ。 | |
| * @returns {void} | |
| */ | |
| const initApp = () => { | |
| const downloadLink = document.getElementById("download-csv"); | |
| const csv = convertJsonToCsv(SAMPLE_DATA); | |
| downloadCsv(downloadLink, csv); | |
| }; | |
| document.addEventListener("DOMContentLoaded", initApp); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment