如何在 Remix 讀取、下載 json 檔
2025-01-04 10:20 Remix
讀取 json
讀取靜態檔案中的 json
流程:
- 使用
node:fs/promises
的readFile
從指定路徑讀取檔案內容 - 使用
JSON.parse()
將字串化的檔案內容解析成物件,接著就可以透過useLoaderData
隨意使用了
import { useLoaderData } from '@remix-run/react';
import { readFile } from 'node:fs/promises';
export async function loader() {
const fileContent = await readFile('path/to/file.json');
return JSON.parse(fileContent.toString());
}
export default function ReadJson() {
const data = useLoaderData<typeof loader>();
}
讀取使用者上傳的 json
重點:
- remix 的
Form
元件encType
要設定為"multipart/form-data"
,參考 TypeError: Could not parse content as FormData - 透過
await new Response(file).text();
即可取得純文字版的 .json 檔內容,加上JSON.parse(text);
就可得到 JS 物件
import type { ActionFunctionArgs, MetaFunction } from '@remix-run/node';
import {
unstable_createMemoryUploadHandler,
unstable_parseMultipartFormData,
} from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
// https://remix.run/docs/en/main/guides/file-uploads
// https://remix.run/docs/en/main/utils/unstable-create-memory-upload-handler
export async function action({ request }: ActionFunctionArgs) {
const uploadHandler = unstable_createMemoryUploadHandler();
const formData = await unstable_parseMultipartFormData(
request,
uploadHandler
);
const file = formData.get('file');
const text = await new Response(file).text();
return JSON.parse(text);
}
export default function Index() {
const data = useActionData<typeof action>(); // do whatever you need
return (
<Form method="post" encType="multipart/form-data">
<input type="file" name="file" accept="application/json" />
<button type="submit">上傳</button>
</Form>
);
}
下載為 json
提醒:我的需求是「把使用者填寫到表單(Form
)中的內容下載為 .json 檔」。如果你會自行處理要被下載的內容,那直接從 downloadJson 這段開始看就好。
使用 qs 處理表單內容
流程:
- 使用者送出(
submit
)表單後,先透過 npm 套件 qs 處理表單內容 - 透過
useActionData
取出 qs 處理好的內容,再搭配downloadJson
(詳細請參考下一大段)執行檔案下載
import type { ActionFunctionArgs } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { useEffect } from 'react';
import qs from 'qs';
import { downloadJson } from '~util';
export async function action({ request }: ActionFunctionArgs) {
const text = await request.text();
return qs.parse(text);
}
export default function SomeForm() {
/* data */
const data = useActionData();
/* hook */
useEffect(() => {
downloadJson(data);
}, []);
/* main */
return (
<Form method="post">
{/* many inputs... */}
<button type="submit">download as .json</button>
</Form>
);
}
function downloadJson
流程:
- 由
JsonHandler
的createUrl
將內容加工成 .json 檔,並取得可下載的路由和下載檔案名稱 - 由
DownloadHandler
負責處理下載行為
export function downloadJson(input: any) {
const jsonHandler = new JsonHandler();
const url = jsonHandler.createUrl(input);
const fileName = jsonHandler.createFileNameDateIso();
const downloadHandler = new DownloadHandler({ url, fileName });
downloadHandler.exec();
}
class JsonHandler
export class JsonHandler {
/**
* Creates a URL for downloading a JSON object.
*
* @param input - The JSON object to be downloaded.
* @param overwriteFormatter - An optional function that formats the JSON object before downloading.
* @returns A URL that can be used to download the JSON object.
*/
createUrl(input: any, overwriteFormatter?: (...args: unknown[]) => string) {
const formatted = overwriteFormatter
? overwriteFormatter(input)
: JSON.stringify(input, null, 2);
const blob = new Blob([formatted], { type: 'application/json' });
return window.URL.createObjectURL(blob);
}
createFileNameDateIso() {
return `${new Date().toISOString()}.json`;
}
}
class DownloadHandler
type DownloadHandlerArgs = {
url: string;
fileName: string;
};
export class DownloadHandler {
#url: string;
#fileName: string;
constructor(args: DownloadHandlerArgs) {
this.#url = args.url;
this.#fileName = args.fileName;
}
createDownloadAnchor(href: string, fileName: string) {
const link = document.createElement('a');
link.href = href;
link.download = fileName;
return link;
}
exec() {
const el = this.createDownloadAnchor(this.#url, this.#fileName);
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
window.URL.revokeObjectURL(this.#url);
}
}
其他下載方式
下載為純文字檔
出自 Remix 的 issue How to download a file in Remix?:
export function action() {
const content = 'some text content...';
return new Response(content, {
status: 200,
headers: {
'Content-Disposition': 'attachment; filename="some.txt"',
'Content-Type': 'plain/text',
},
});
}
下載為 .md 檔
出自 stack overflow Remix: file download:
import { createReadableStreamFromReadable } from '@remix-run/node';
import { Readable } from 'node:stream';
export const loader = async () => {
const file = createReadableStreamFromReadable(
Readable.from(['Hello, World!'])
);
return new Response(file, {
headers: {
'Content-Disposition': 'attachment; filename="hello.md"',
'Content-Type': 'text/markdown',
},
});
};
參考文件
讀取 json 檔:
- Remix: File Uploads
- Remix: unstable_createMemoryUploadHandler
- MDN: Blob > Extracting data from a blob
下載為 json 檔: