ข้ามไปยังเนื้อหา
Toova
เครื่องมือทั้งหมด

วิธี Format JSON ใน JavaScript — 5 วิธี

Toova

JSON อยู่ทุกที่: API response, ไฟล์ config, การ export ฐานข้อมูล, ไปป์ไลน์ log แต่ output JSON ดิบ มักเป็น blob กระทัดรัด — ไม่มีบรรทัดใหม่, ไม่มีการเยื้อง, คีย์เรียงตามที่ serializer พ่นออก มันทำให้การดีบักเจ็บปวดและการทำงานร่วมกันยากกว่าที่ควร

JavaScript ให้หลายวิธี format JSM จาก JSON.stringify ในตัวไปจนถึง library streaming สำหรับไฟล์ใหญ่เกินกว่าจะโหลดเข้า memory คู่มือนี้พาผ่านห้าวิธีที่ปฏิบัติได้ ครอบคลุมกรณีที่พบบ่อยที่สุดและกรณีพิเศษที่จับนักพัฒนาแบบไม่ทันตั้งตัว

วิธี 1: JSON.stringify พร้อมการเยื้อง

วิธีที่ง่ายที่สุดอยู่ในภาษาแล้ว JSON.stringify รับสามอาร์กิวเมนต์: ค่าที่จะ serialize, replacer (จะมีในวิธี 2) และพารามิเตอร์ space ที่ควบคุมการเยื้อง

const data = {
  name: "Alice",
  role: "engineer",
  skills: ["JavaScript", "TypeScript", "Node.js"],
  active: true,
};

// เยื้องช่องว่าง 2 (ค่าเริ่มต้นทั่วไป)
console.log(JSON.stringify(data, null, 2));

// เยื้องช่องว่าง 4
console.log(JSON.stringify(data, null, 4));

// เยื้อง tab
console.log(JSON.stringify(data, null, "\t"));

ด้วย null, 2, output ดูเช่นนี้:

{
  "name": "Alice",
  "role": "engineer",
  "skills": [
    "JavaScript",
    "TypeScript",
    "Node.js"
  ],
  "active": true
}

ช่องว่างสองคือธรรมเนียมที่พบบ่อยที่สุดในโปรเจกต์ JavaScript — ตรงกับค่าเริ่มต้นใน ESLint, Prettier และ style guide ส่วนใหญ่ ช่องว่างสี่พบบ่อยในเครื่องมือใกล้ Python เมื่อ space ละไว้หรือตั้งเป็น 0, output เป็นสตริงบรรทัดเดียวกระทัดรัด — เหมาะสำหรับการส่งผ่าน

คุณยังสามารถ format JSON ทันทีในเบราว์เซอร์โดยใช้ เครื่องมือ JSON Formatter — paste JSON ดิบ, ได้ output ที่ pretty-print แล้ว และคัดลอกผลโดยไม่ต้องเขียนโค้ดใดๆ

วิธี 2: Custom Replacer Function

อาร์กิวเมนต์ที่สองของ JSON.stringify — replacer — เป็น function หรือ array ที่ กรองและแปลงค่าก่อนการ serialize นี่คือที่คุณได้ควบคุมจริงต่อสิ่งที่จบใน output

รูปแบบ array คือวิธีที่ง่ายที่สุดสำหรับการ whitelist คีย์:

const user = {
  id: 42,
  name: "Bob",
  password: "s3cr3t",
  email: "bob@example.com",
  createdAt: new Date(),
};

// Array replacer: รวมเฉพาะคีย์ที่ระบุ
const safeJson = JSON.stringify(user, ["id", "name", "email"], 2);
console.log(safeJson);
// {
//   "id": 42,
//   "name": "Bob",
//   "email": "bob@example.com"
// }

รูปแบบ function ให้คุณควบคุมต่อค่า ส่งคืนค่าเพื่อรวม, ส่งคืน undefined เพื่อทิ้ง หรือส่งคืนค่าที่แปลง:

function replacer(key, value) {
  // ทิ้งคีย์ที่เริ่มต้นด้วย underscore (ธรรมเนียม private)
  if (key.startsWith("_")) return undefined;

  // ซ่อนฟิลด์ที่ละเอียดอ่อน
  if (key === "password" || key === "token") return "[REDACTED]";

  // แปลง Date object เป็นสตริง ISO โดยชัดเจน
  if (value instanceof Date) return value.toISOString();

  return value;
}

const payload = {
  id: 1,
  name: "Carol",
  password: "hunter2",
  _internalFlag: true,
  lastLogin: new Date("2026-05-01"),
};

console.log(JSON.stringify(payload, replacer, 2));
// {
//   "id": 1,
//   "name": "Carol",
//   "password": "[REDACTED]",
//   "lastLogin": "2026-05-01T00:00:00.000Z"
// }

Replacer รันแบบ recursive บนทุก object และ array ซ้อน รูปแบบนี้มีประโยชน์สำหรับไปป์ไลน์ logging ที่ต้องการตัด credential ออกก่อนเขียนลงดิสก์หรือส่งไปยังบริการ observability

วิธี 3: Pretty-Print พร้อมเรียงคีย์

JavaScript object ไม่รับประกันลำดับคีย์ (แม้ว่า V8 และ engine ส่วนใหญ่จะรักษาลำดับ insert สำหรับคีย์สตริง) เมื่อต้องการ output แบบ deterministic — สำหรับการ diff, การ cache หรือการแทนแบบ canonical — การเรียงคีย์ตามตัวอักษรเป็นการเคลื่อนที่ถูก

function sortedStringify(value, indent = 2) {
  return JSON.stringify(value, sortReplacer, indent);
}

function sortReplacer(key, value) {
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
    return Object.keys(value)
      .sort()
      .reduce((sorted, k) => {
        sorted[k] = value[k];
        return sorted;
      }, {});
  }
  return value;
}

const config = {
  version: "1.0",
  author: "Dave",
  dependencies: { typescript: "^5.4", eslint: "^9.0", astro: "^5.0" },
  name: "my-project",
};

console.log(sortedStringify(config));
// {
//   "author": "Dave",
//   "dependencies": { "astro": "^5.0", "eslint": "^9.0", "typescript": "^5.4" },
//   "name": "my-project",
//   "version": "1.0"
// }

คีย์ที่เรียงหมายถึง git diff แสดงเฉพาะบรรทัดที่เปลี่ยนจริง แทนการเรียงใหม่แบบสุ่ม จาก serializer ต่างกัน นี่มีประโยชน์โดยเฉพาะสำหรับ package.json และไฟล์ config คล้ายที่ commit เข้า version control

สำหรับการแปลง JSON เป็น format อื่น, เครื่องมือ JSON to YAML และ JSON to CSV ก็จัดการลำดับคีย์ใน output ของพวกมัน

วิธี 4: Format จากสตริง (Parse + Stringify)

JSON โลกจริงมักมาเป็นสตริง — จาก fetch response, การอ่านไฟล์, การ paste คลิปบอร์ด หรือคอลัมน์ TEXT ฐานข้อมูล คุณต้อง parse ก่อน แล้ว reformat ส่วนที่สำคัญคือการจัดการ error เหมาะ: JSON ที่ไม่ถูกต้องจะ throw และคุณต้องการจับเหตุนั้นอย่างสง่างาม

function formatJsonString(rawString, indent = 2) {
  try {
    const parsed = JSON.parse(rawString);
    return { ok: true, result: JSON.stringify(parsed, null, indent) };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

const raw = '{"name":"Eve","scores":[100,95,88],"active":true}';
const { ok, result, error } = formatJsonString(raw);

if (ok) {
  console.log(result);
  // {
  //   "name": "Eve",
  //   "scores": [100, 95, 88],
  //   "active": true
  // }
} else {
  console.error("Parsing ล้มเหลว:", error);
}

// Input ไม่ถูกต้อง
const bad = '{"name": "Eve", "broken":}';
const r2 = formatJsonString(bad);
// { ok: false, error: "Unexpected token '}'" }

การห่อ error parse ในผลส่งคืนที่มีโครงสร้างทำให้ฟังก์ชันนี้ปลอดภัยใช้ในคอมโพเนนต์ UI และสคริปต์ build โดยไม่ต้องห่อทุกจุดเรียกใน try/catch เอกสาร MDN สำหรับ JSON.stringify ครอบคลุม spec พารามิเตอร์เต็ม และ RFC 8259 นิยามว่า JSON ที่ถูกต้องดูเป็นอย่างไรในระดับโปรโตคอล

วิธี 5: Streaming สำหรับไฟล์ขนาดใหญ่

วิธี 1-4 ทั้งหมดโหลดโครงสร้าง JSON ทั้งหมดเข้า memory ก่อน format สำหรับไฟล์ใน หลายร้อยเมกะไบต์หรือช่วงหลายกิกะไบต์ นี่บล็อก event loop Node.js และอาจล้ม process ทั้งหมด

วิธี streaming อ่านไฟล์เป็นชุดและเขียน output ที่ format เพิ่มทีละน้อย สำหรับ NDJSON (หนึ่ง JSON object ต่อบรรทัด พบบ่อยในไฟล์ log และการ export ฐานข้อมูล), วิธีที่ใช้ readline ทำงานได้โดยไม่ต้อง dependency เพิ่ม:

import { createReadStream, createWriteStream } from "node:fs";
import { createInterface } from "node:readline";

async function formatNdjsonFile(inputPath, outputPath) {
  const rl = createInterface({
    input: createReadStream(inputPath),
    crlfDelay: Infinity,
  });

  const output = createWriteStream(outputPath);

  for await (const line of rl) {
    if (!line.trim()) continue;
    try {
      const obj = JSON.parse(line);
      output.write(JSON.stringify(obj, null, 2) + "\n---\n");
    } catch (e) {
      output.write("[บรรทัด JSON ไม่ถูกต้อง: " + e.message + "]\n---\n");
    }
  }

  output.end();
}

NDJSON คือ format streaming ที่ง่ายที่สุด: แต่ละบรรทัดเป็น JSON object ที่ถูกต้องสมบูรณ์ เครื่องมือ export หลายตัวรองรับมันตรงเพราะ stream ได้ง่าย หากคุณควบคุม format ของการ export ข้อมูลขนาดใหญ่ เลือก NDJSON เหนือ array JSON ขนาดยักษ์ตัวเดียว

กรณีพิเศษที่ต้องระวัง

สิ่งเหล่านี้คือสถานการณ์ที่การ format JSON มาตรฐานล้มเหลวเงียบๆ หรือ throw แบบไม่คาดคิด

Circular Reference

JSON.stringify throw TypeError หาก object อ้างถึงตัวเองโดยตรง หรือทางอ้อม แก้ไขด้วย replacer ที่ติดตาม object ที่เยี่ยมแล้วโดยใช้ WeakSet:

function safeStringify(obj, indent = 2) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) return "[Circular]";
      seen.add(value);
    }
    return value;
  }, indent);
}

const a = { name: "circular" };
a.self = a;
console.log(safeStringify(a));
// { "name": "circular", "self": "[Circular]" }

WeakSet ถือ reference โดยไม่ป้องกันการ garbage collect ซึ่งหลีกเลี่ยงการรั่ว memory ในการรัน process ระยะยาว

ค่า BigInt

JSON.stringify throw TypeError สำหรับค่า BigInt เพราะ spec JSON ไม่มี integer 64-bit type แปลงเป็นสตริงใน replacer ของคุณ:

const data = { id: 9007199254740993n, value: 42 };

JSON.stringify(data, (key, value) =>
  typeof value === "bigint" ? value.toString() : value
, 2);
// { "id": "9007199254740993", "value": 42 }

ค่า Map และ Set

Map ถูก serialize เป็น object ว่างและ Set ถูก serialize เป็น array ว่าง — ไม่ใช่เนื้อหาของพวกมัน แปลงพวกมันโดยชัดเจนใน replacer:

const data = {
  tags: new Set(["json", "javascript"]),
  meta: new Map([["source", "api"]]),
};

JSON.stringify(data, (key, value) => {
  if (value instanceof Set) return [...value];
  if (value instanceof Map) return Object.fromEntries(value);
  return value;
}, 2);
// { "tags": ["json", "javascript"], "meta": { "source": "api" } }

ค่า undefined

คุณสมบัติ object ที่มีค่า undefined ถูกทิ้งแบบเงียบๆ Array slot ที่มี undefined กลายเป็น null ใช้ replacer แปลง undefined เป็น null เมื่อต้องการรักษาทุกคีย์:

const obj = { a: 1, b: undefined, c: null };
JSON.stringify(obj, null, 2);
// { "a": 1, "c": null }  — "b" ถูกทิ้งแบบเงียบๆ

// แก้ไข:
JSON.stringify(obj, (key, value) =>
  value === undefined ? null : value
, 2);
// { "a": 1, "b": null, "c": null }

Format JSON ทันทีในเบราว์เซอร์

หากต้องการ format blob JSON ตอนนี้โดยไม่ต้องเขียนโค้ดใดๆ, Toova JSON Formatter จัดการในคลิกเดียว — paste JSON ดิบ, ได้ output ที่ pretty-print ด้วยการเยื้องช่องว่าง 2 หรือ 4 และคัดลอกผล ไม่ต้องสมัคร, ไม่ต้องอัปโหลดไฟล์, ทุกอย่างรันในเบราว์เซอร์ของคุณในเครื่อง

สำหรับการแปลงระหว่าง format, JSON to YAML และ JSON to CSV ตามวิธีเน้นความเป็นส่วนตัวก่อนแบบเดียวกัน — ข้อมูลของคุณไม่เคย ออกจากอุปกรณ์

บทสรุป

สำหรับการใช้งานส่วนใหญ่, JSON.stringify(obj, null, 2) คือทั้งหมดที่คุณต้องการ เพิ่ม replacer function เมื่อต้องการการกรอง, การ mask หรือคีย์ที่เรียง ห่อ JSON.parse ใน try/catch เมื่อจัดการ input ภายนอก เอื้อมหา streaming เฉพาะเมื่อขนาดไฟล์ทำให้การ parse แบบ synchronous ไม่ปฏิบัติได้ และเก็บกรณีพิเศษ — circular ref, BigInt, Map/Set, undefined — ไว้ในใจเมื่อทำงานกับ รูปร่างข้อมูลที่ไม่ธรรมดา