讀取 json
讀取靜態檔案中的 json
流程:
- 使用
node:fs/promises的readFile從指定路徑讀取檔案內容 - 使用
JSON.parse()將字串化的檔案內容解析成物件,接著就可以透過useLoaderData隨意使用了
import { readFile } from "node:fs/promises";import { useLoaderData } from "@remix-run/react";
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-handlerexport 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 { downloadJson } from "~util";import qs from "qs";import { useEffect } from "react";
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 { Readable } from "node:stream";import { createReadableStreamFromReadable } from "@remix-run/node";
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 檔: