CSV to JSON JavaScript — Converter + Code Examples

·Front-end & Node.js Developer·Đánh giá bởiSophie Laurent·Đã xuất bản

Sử dụng Chuyển CSV sang JSON miễn phí trực tiếp trên trình duyệt — không cần cài đặt.

Dùng thử Chuyển CSV sang JSON trực tuyến →

Hầu hết dữ liệu CSV tôi gặp đến dưới dạng chuỗi phẳng từ tải tệp lên, xuất cơ sở dữ liệu, hoặc API vẫn dùng định dạng của thập niên 1970. Để chuyển đổi CSV sang JSON trong JavaScript, bạn cần hai thứ mà ngôn ngữ cung cấp miễn phí: tách chuỗi để phân tích các hàng và JSON.stringify() để chuyển đổi kết quả. Không cần gói npm nào cho những thao tác cơ bản — hướng dẫn này bao gồm toàn bộ quy trình từ tiện ích csvToJson() có thể tái sử dụng đến PapaParse và file I/O trong Node.js. Để chuyển đổi nhanh mà không cần code, công cụ chuyển đổi CSV sang JSON trực tuyến xử lý ngay lập tức. Tất cả ví dụ nhắm đến Node.js 18+ và các trình duyệt hiện đại.

  • Tách CSV theo ký tự xuống dòng, trích xuất tiêu đề từ hàng 0, ánh xạ các hàng còn lại thành đối tượng, rồi dùng JSON.stringify(array, null, 2) để có đầu ra đẹp.
  • JSON.stringify() tạo ra chuỗi; JSON.parse() chuyển đổi lại thành mảng JavaScript — hãy biết bạn đang có cái nào trước khi thao tác.
  • Các thực thể Map không được chuyển đổi thành JSON tự động — hãy gọi Object.fromEntries(map) trước.
  • Với CSV có trường có dấu ngoặc kép, dấu phẩy bên trong giá trị hoặc ký tự xuống dòng trong ô, hãy dùng PapaParse hoặc csv-parse thay vì tách thủ công.
  • csvtojson (npm) xử lý ép kiểu, streaming và các trường hợp đặc biệt RFC 4180 trong một lần gọi.

Chuyển đổi CSV sang JSON là gì?

Chuyển đổi CSV sang JSON biến đổi định dạng văn bản phẳng phân tách bằng dấu phẩy thành một mảng có cấu trúc gồm các đối tượng, trong đó mỗi hàng trở thành một đối tượng JavaScript có khóa là tiêu đề cột. Định dạng CSV không có kiểu dữ liệu — mọi thứ đều là chuỗi. JSON bổ sung cấu trúc, lồng nhau và kiểu tường minh (số, boolean, null). Quá trình chuyển đổi này là bước đầu tiên trong hầu hết mọi quy trình xử lý dữ liệu bắt đầu từ xuất bảng tính, kết xuất hệ thống cũ hoặc tệp được người dùng tải lên. Dữ liệu nền tảng không thay đổi; định dạng thay đổi từ các cột dựa trên vị trí sang các thuộc tính được đặt tên.

Before · json
After · json
name,email,role,active
Nguyễn Văn An,nvan@nexuslabs.io,Engineering Lead,true
Trần Thị Hoa,tthoa@nexuslabs.io,Product Manager,true
[
  {
    "name": "Nguyễn Văn An",
    "email": "nvan@nexuslabs.io",
    "role": "Engineering Lead",
    "active": "true"
  },
  {
    "name": "Trần Thị Hoa",
    "email": "tthoa@nexuslabs.io",
    "role": "Product Manager",
    "active": "true"
  }
]

csvToJson() — Xây dựng hàm chuyển đổi có thể tái sử dụng

Toàn bộ quy trình CSV-to-JSON trong JavaScript chia thành ba bước: tách chuỗi CSV theo ký tự xuống dòng để lấy các hàng, trích xuất tiêu đề từ hàng đầu tiên bằng split(','), sau đó ánh xạ mỗi hàng còn lại thành một đối tượng JavaScript thuần túy với khóa từ tiêu đề và giá trị từ các vị trí cột tương ứng. Lời gọi cuối cùng đến JSON.stringify() chuyển đổi mảng đối tượng đó thành chuỗi JSON. Đây là phiên bản tối giản hoạt động được:

JavaScript — minimal csvToJson
function csvToJson(csv) {
  const lines = csv.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())

  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(
        headers.map((header, i) => [header, values[i]?.trim()])
      )
    })

  return JSON.stringify(rows, null, 2)
}

const csv = `server,port,region,status
api-gateway,8080,us-east-1,healthy
auth-service,8443,eu-west-1,degraded
payments-api,9090,ap-south-1,healthy`

console.log(csvToJson(csv))
// [
//   { "server": "api-gateway", "port": "8080", "region": "us-east-1", "status": "healthy" },
//   { "server": "auth-service", "port": "8443", "region": "eu-west-1", "status": "degraded" },
//   { "server": "payments-api", "port": "9090", "region": "ap-south-1", "status": "healthy" }
// ]

Hàm đó xử lý những điều cơ bản: ký tự xuống dòng ở cuối, dòng trống, khoảng trắng xung quanh giá trị. Mọi trường CSV đều được xử lý dưới dạng chuỗi. Lưu ý rằng port "8080" (một chuỗi), không phải 8080 (một số). Nếu bạn cần kiểu dữ liệu đúng trong đầu ra JSON, bạn phải tự ép kiểu chúng. Đây là phiên bản mở rộng với tính năng phát hiện kiểu:

JavaScript — csvToJson with type coercion
function coerceValue(val) {
  if (val === undefined || val === '') return null
  if (val === 'true') return true
  if (val === 'false') return false
  if (val === 'null') return null
  const num = Number(val)
  if (!isNaN(num) && val.trim() !== '') return num
  return val
}

function csvToJson(csv, { coerce = false } = {}) {
  const lines = csv.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())

  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(
        headers.map((header, i) => [
          header,
          coerce ? coerceValue(values[i]?.trim()) : values[i]?.trim()
        ])
      )
    })

  return JSON.stringify(rows, null, 2)
}

const csv = `endpoint,port,max_connections,debug
/api/v2/orders,8443,500,true
/api/v2/health,8080,100,false`

console.log(csvToJson(csv, { coerce: true }))
// [
//   { "endpoint": "/api/v2/orders", "port": 8443, "max_connections": 500, "debug": true },
//   { "endpoint": "/api/v2/health", "port": 8080, "max_connections": 100, "debug": false }
// ]

Cờ coerce là tùy chọn vì tính năng phát hiện kiểu tự động có thể phản tác dụng — một trường như mã bưu chính ("07302") mất số không đầu khi chuyển đổi sang số. Giữ ép kiểu tắt theo mặc định và chỉ bật khi bạn kiểm soát schema. Lưu ý nhanh: JSON.stringify() chấp nhận tham số thứ ba là space để thụt lề. Truyền 2 cho hai dấu cách, 4 cho bốn, hoặc "\t" cho tab. Bỏ qua hoàn toàn để có đầu ra gọn một dòng — hữu ích khi gửi chuỗi JSON làm thân yêu cầu API, nơi khoảng trắng chỉ lãng phí băng thông.

Lưu ý:Chuỗi CSV thô không phải JSON. Gọi JSON.parse() trực tiếp trên văn bản CSV sẽ ném ra SyntaxError. Bạn phải trước tiên chuyển đổi CSV thành các đối tượng JavaScript bằng hàm csvToJson() của bạn, hàm này nội bộ gọi JSON.stringify() để tạo ra chuỗi JSON thực sự.

Xử lý Map, Date và các đối tượng tùy chỉnh từ dữ liệu CSV

Không phải mọi quá trình chuyển đổi CSV đều kết thúc với mảng phẳng các đối tượng thông thường. Đôi khi bạn cần xây dựng một Map từ các cặp tiêu đề-giá trị, phân tích chuỗi ngày thành đối tượng Date, hoặc đính kèm các thuộc tính tính toán trước khi chuyển đổi. JavaScript có một điểm quirk khiến người ta dễ nhầm: các thực thể Map không được chuyển đổi với JSON.stringify(). Bạn sẽ nhận được một đối tượng rỗng. Cách sửa là dùng Object.fromEntries() để chuyển Map trở lại thành đối tượng thông thường trước khi stringify.

Lý do Map serialize thành {} là vì JSON.stringify() lặp qua các thuộc tính enumerable riêng của một đối tượng. Map lưu trữ các phần tử của nó trong một slot nội bộ, không phải dưới dạng thuộc tính enumerable trên chính đối tượng, vì vậy bộ serialize thấy một đối tượng không có khóa. Prototype của Map cũng thiếu phương thức toJSON(), đây là hook mà JSON.stringify() gọi đầu tiên trên bất kỳ giá trị nào trước khi quyết định cách serialize. Nếu một giá trị có toJSON(), giá trị trả về của phương thức đó là thứ được serialize — không phải đối tượng chính nó. Đó là lý do tại sao các đối tượng Date serialize đúng cách: Date.prototype.toJSON trả về chuỗi ISO 8601, vì vậy JSON.stringify(new Date()) tạo ra một timestamp được trích dẫn thay vì đối tượng rỗng. Hiểu hook này cho phép bạn định nghĩa hành vi tương tự trên các class của riêng bạn — như được hiển thị trong ví dụ EmployeeRecord bên dưới — để kiểm soát chính xác những trường nào từ CSV xuất hiện trong đầu ra JSON cuối cùng.

Chuyển đổi Map sang JSON

JavaScript — Map to JSON via Object.fromEntries()
// Xây dựng Map từ các cặp tiêu đề→giá trị CSV
const headers = ['server', 'port', 'region']
const values = ['payments-api', '9090', 'ap-south-1']
const rowMap = new Map(headers.map((h, i) => [h, values[i]]))

// Map KHÔNG serialize trực tiếp
console.log(JSON.stringify(rowMap))
// "{}"  — đối tượng rỗng, dữ liệu bị mất!

// Chuyển sang đối tượng thông thường trước
const rowObj = Object.fromEntries(rowMap)
console.log(JSON.stringify(rowObj, null, 2))
// {
//   "server": "payments-api",
//   "port": "9090",
//   "region": "ap-south-1"
// }

Chuỗi ngày tháng và toJSON()

Các trường ngày tháng trong CSV đến dưới dạng chuỗi. Nếu bạn phân tích chúng thành đối tượng Date trong quá trình xử lý, những Date đó serialize đúng cách vì Date có phương thức toJSON() tích hợp sẵn trả về chuỗi ISO 8601. Bạn cũng có thể định nghĩa toJSON() tùy chỉnh trên các class của riêng bạn để kiểm soát cột CSV nào xuất hiện trong đầu ra serialize — ví dụ: bỏ qua các trường theo dõi nội bộ như _rowIndex.

JavaScript — toJSON() for custom serialization
class EmployeeRecord {
  constructor(csvRow) {
    this._rowIndex = csvRow._rowIndex  // nội bộ, không dùng cho JSON
    this.employeeId = csvRow.employee_id
    this.name = csvRow.name
    this.hiredAt = new Date(csvRow.hired_date)
    this.salary = Number(csvRow.salary)
  }

  toJSON() {
    // Chỉ hiển thị các trường chúng ta muốn trong đầu ra JSON
    return {
      employee_id: this.employeeId,
      name: this.name,
      hired_at: this.hiredAt,  // Date.toJSON() → chuỗi ISO tự động
      salary: this.salary,
    }
  }
}

const csvRow = {
  _rowIndex: 42,
  employee_id: 'EMP-2847',
  name: 'Nguyễn Văn An',
  hired_date: '2024-03-15',
  salary: '128000',
}

const record = new EmployeeRecord(csvRow)
console.log(JSON.stringify(record, null, 2))
// {
//   "employee_id": "EMP-2847",
//   "name": "Nguyễn Văn An",
//   "hired_at": "2024-03-15T00:00:00.000Z",
//   "salary": 128000
// }
// Lưu ý: _rowIndex bị loại trừ, salary là số, ngày ở định dạng ISO

Reviver để giải serialize ngày tháng

Sau khi ghi JSON từ CSV vào tệp hoặc gửi qua mạng, JSON.parse() trả về các đối tượng thông thường — các đối tượng Date trở thành chuỗi trở lại. Dùng hàm reviver để chuyển đổi chuỗi ISO 8601 trở lại thành đối tượng Date trong quá trình phân tích:

JavaScript — reviver to reconstruct Date objects
const jsonString = '{"employee_id":"EMP-2847","name":"Nguyễn Văn An","hired_at":"2024-03-15T00:00:00.000Z","salary":128000}'

const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/

const parsed = JSON.parse(jsonString, (key, value) => {
  if (typeof value === 'string' && isoDatePattern.test(value)) {
    return new Date(value)
  }
  return value
})

console.log(parsed.hired_at instanceof Date)  // true
console.log(parsed.hired_at.getFullYear())    // 2024
Cảnh báo:JSON.stringify() trả về undefined (không phải chuỗi) với các giá trị chứa hàm, Symbol, hoặc các thuộc tính undefined. Nếu các đối tượng từ CSV của bạn vô tình nhận giá trị undefined từ một cột bị thiếu, thuộc tính đó sẽ biến mất trong đầu ra JSON. Luôn dùng mặc định null cho các giá trị bị thiếu.

Tham chiếu tham số JSON.stringify()

Chữ ký hàm là JSON.stringify(value, replacer?, space?). Hai tham số replacer space đều tùy chọn — truyền null cho replacer khi bạn chỉ cần thụt lề.

Tham số
Kiểu
Mặc định
Mô tả
value
any
(bắt buộc)
Giá trị JavaScript cần chuyển đổi thành chuỗi — đối tượng, mảng, chuỗi, số, boolean hoặc null
replacer
Function | Array | null
undefined
Lọc hoặc biến đổi giá trị trong quá trình chuyển đổi. Mảng giới hạn tên thuộc tính; hàm nhận các cặp (key, value)
space
number | string
undefined
Thụt lề: số 0–10 cho dấu cách, chuỗi (ví dụ "\t") cho thụt lề tùy chỉnh. Bỏ qua để xuất ra dạng một dòng gọn

Tham số của JSON.parse():

Tham số
Kiểu
Mặc định
Mô tả
text
string
(bắt buộc)
Chuỗi JSON cần phân tích — phải là JSON hợp lệ, nếu không sẽ ném ra SyntaxError
reviver
Function | undefined
undefined
Được gọi cho mỗi cặp key-value. Giá trị trả về thay thế giá trị gốc; trả về undefined để xóa thuộc tính

JSON.parse() — Sử dụng đầu ra JSON

Khi bạn có chuỗi JSON từ csvToJson(), bước tiếp theo thường là phân tích lại thành mảng JavaScript để lọc, ánh xạ hoặc đưa vào API. Sự khác biệt giữa chuỗi JSON (typeof === "string") và đối tượng JavaScript là quan trọng. Bạn không thể gọi .filter() hoặc truy cập [0].name trên chuỗi — bạn cần JSON.parse() trước. Vòng tròn này (stringify rồi parse) cũng hoạt động như một kỹ thuật xác thực: nếu quá trình chuyển đổi CSV tạo ra thứ không phải JSON hợp lệ, parse sẽ ném ra ngoại lệ. Tham số reviver tùy chọn cho phép bạn biến đổi mỗi cặp key-value trong quá trình phân tích — hữu ích để khôi phục đối tượng Date từ chuỗi ISO hoặc đổi tên khóa mà không cần bước riêng biệt.

JavaScript — parse JSON output and query rows
const csv = `endpoint,method,avg_latency_ms,error_rate
/api/v2/orders,POST,342,0.02
/api/v2/health,GET,12,0.00
/api/v2/payments,POST,890,0.15
/api/v2/users,GET,45,0.01`

// Bước 1: chuyển đổi CSV sang chuỗi JSON
const jsonString = csvToJson(csv, { coerce: true })

// Bước 2: phân tích lại thành mảng JavaScript
const endpoints = JSON.parse(jsonString)

// Xác nhận đây là mảng
console.log(Array.isArray(endpoints))  // true

// Lọc các endpoint có độ trễ cao
const slow = endpoints.filter(ep => ep.avg_latency_ms > 200)
console.log(slow.map(ep => ep.endpoint))
// ["/api/v2/orders", "/api/v2/payments"]

// Destructure hàng đầu tiên
const [first, ...rest] = endpoints
console.log(first.endpoint)  // "/api/v2/orders"
console.log(rest.length)     // 3

Một wrapper an toàn cho JSON.parse() hữu ích khi xác thực đầu ra chuyển đổi trước khi xử lý tiếp theo. Nếu quá trình chuyển đổi CSV tạo ra JSON không đúng định dạng vì bất kỳ lý do nào (đầu vào bị cắt ngắn, lỗi mã hóa), cách này bắt lỗi mà không gây ra sự cố:

JavaScript — safe parse wrapper
function safeParse(jsonString) {
  try {
    return { data: JSON.parse(jsonString), error: null }
  } catch (err) {
    return { data: null, error: err.message }
  }
}

// Đầu ra hợp lệ
const result = safeParse(csvToJson(csv))
if (result.error) {
  console.error('CSV conversion produced invalid JSON:', result.error)
} else {
  console.log(`Parsed ${result.data.length} rows`)
}

// Vô tình truyền CSV thô vào JSON.parse — điều này thất bại
const bad = safeParse('name,email\nNguyễn Văn An,nvan@nexuslabs.io')
console.log(bad.error)  // "Unexpected token 'a', "name,email"... is not valid JSON"

Reviver để đổi tên khóa và xác thực

Hàm reviver nhận mỗi cặp key-value trong quá trình phân tích, từ các thuộc tính trong cùng ra ngoài. Trả về undefined cho một khóa sẽ xóa nó khỏi kết quả hoàn toàn; trả về giá trị khác sẽ thay thế nó. Reviver hữu ích để đổi tên tiêu đề (camelCase sang snake_case), loại bỏ các trường nội bộ, hoặc kiểm tra sự tồn tại của các cột bắt buộc. Nó được gọi với giá trị gốc cuối cùng (khóa chuỗi rỗng), đây là nơi bạn ném ra ngoại lệ nếu kết quả không phải mảng.

JavaScript — reviver for key renaming and shape validation
const jsonString = csvToJson(`employeeId,firstName,hiredDate
EMP-2847,Nguyễn Văn An,2024-03-15
EMP-3012,Trần Thị Hoa,2023-11-01`, { coerce: false })

const camelToSnake = str => str.replace(/[A-Z]/g, c => '_' + c.toLowerCase())

const employees = JSON.parse(jsonString, function(key, value) {
  // Giá trị gốc — xác thực cấu trúc
  if (key === '') {
    if (!Array.isArray(value)) throw new Error('Expected JSON array from CSV')
    return value
  }
  // Đổi tên khóa camelCase sang snake_case
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
    return Object.fromEntries(
      Object.entries(value).map(([k, v]) => [camelToSnake(k), v])
    )
  }
  return value
})

console.log(employees[0])
// { employee_id: 'EMP-2847', first_name: 'Nguyễn Văn An', hired_date: '2024-03-15' }

Chuyển đổi CSV từ tệp và phản hồi API

Hai nơi dữ liệu CSV thực sự đến trong môi trường sản xuất: các tệp trên ổ đĩa và các phản hồi HTTP. Cả hai kịch bản đều cần xử lý lỗi vì đầu vào là bên ngoài và không được kiểm soát.

Đọc tệp CSV, chuyển đổi, ghi JSON

Node.js 18+ — file conversion
import { readFileSync, writeFileSync } from 'node:fs'

function csvToJsonFromFile(inputPath, outputPath) {
  let csvText
  try {
    csvText = readFileSync(inputPath, 'utf8')
  } catch (err) {
    throw new Error(`Failed to read ${inputPath}: ${err.message}`)
  }

  const lines = csvText.trim().split('\n')
  if (lines.length < 2) {
    throw new Error(`${inputPath} has no data rows (only ${lines.length} line)`)
  }

  const headers = lines[0].split(',').map(h => h.trim())
  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
    })

  const jsonOutput = JSON.stringify(rows, null, 2)
  writeFileSync(outputPath, jsonOutput, 'utf8')
  console.log(`Converted ${rows.length} rows → ${outputPath}`)
  return rows
}

// Cách dùng
const data = csvToJsonFromFile('inventory.csv', 'inventory.json')
console.log(data[0])
// { sku: "WDG-2847", warehouse: "us-east-1", quantity: "150", ... }

Lấy CSV từ endpoint API

Node.js 18+ — API response conversion
async function fetchCsvAsJson(url) {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  const contentType = response.headers.get('content-type') || ''
  if (!contentType.includes('text/csv') && !contentType.includes('text/plain')) {
    console.warn(`Unexpected content-type: ${contentType}`)
  }

  const csvText = await response.text()
  const lines = csvText.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())
  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
    })

  return rows
}

// Ví dụ: lấy CSV tỷ giá hối đoái từ nhà cung cấp dữ liệu
try {
  const rates = await fetchCsvAsJson('https://data.ecb.internal/rates/daily.csv')
  console.log(JSON.stringify(rates.slice(0, 3), null, 2))
  // Gửi dưới dạng JSON đến dịch vụ tiếp theo
  await fetch('https://api.internal/v2/rates', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data: rates }),
  })
} catch (err) {
  console.error('Rate sync failed:', err.message)
}
Lưu ý:Tham số replacer trong JSON.stringify() cho phép bạn chọn các cột cụ thể từ CSV. Truyền một mảng tên tiêu đề để chỉ bao gồm những trường đó: JSON.stringify(rows, ['name', 'email', 'department']). Các thuộc tính không có trong mảng sẽ bị loại trừ trong đầu ra mà không có thông báo.

Chuyển đổi CSV sang JSON qua dòng lệnh

Node.js có thể chạy các script nội tuyến, và có những công cụ CLI chuyên dụng xử lý chuyển đổi CSV-to-JSON mà không cần viết script.

bash — Node.js one-liner
# Pipe CSV vào script nội tuyến Node.js
cat servers.csv | node -e "
  const lines = require('fs').readFileSync('/dev/stdin','utf8').trim().split('\n');
  const h = lines[0].split(',');
  const rows = lines.slice(1).map(l => Object.fromEntries(h.map((k,i) => [k.trim(), l.split(',')[i]?.trim()])));
  console.log(JSON.stringify(rows, null, 2));
"
bash — Miller (mlr) for CSV to JSON
# Miller là công cụ đa năng cho dữ liệu có cấu trúc
# Cài đặt: brew install miller (macOS) hoặc apt install miller (Debian/Ubuntu)
mlr --icsv --ojson cat inventory.csv

# Lọc hàng trong quá trình chuyển đổi
mlr --icsv --ojson filter '$quantity > 100' inventory.csv

# Chọn các cột cụ thể
mlr --icsv --ojson cut -f sku,warehouse,quantity inventory.csv
bash — csvtojson CLI
# Cài đặt toàn cục
npm install -g csvtojson

# Chuyển đổi tệp
csvtojson servers.csv > servers.json

# Pipe từ stdin
cat exports/q1-metrics.csv | csvtojson > q1-metrics.json

Với các tệp lớn, Miller thường là lựa chọn tốt hơn csvtojson. Miller được triển khai bằng C và xử lý CSV như một luồng mà không tải toàn bộ tệp vào bộ nhớ, có nghĩa là nó xử lý các xuất dữ liệu nhiều gigabyte với mức sử dụng bộ nhớ không đổi. Nó cũng hỗ trợ các thao tác cấp trường tại chỗ — đổi tên cột, ép kiểu giá trị, lọc hàng — trước khi dữ liệu trở thành JSON, giúp bạn tránh quy trình hai bước parse-rồi-biến-đổi. Ngược lại, csvtojson chạy trong Node.js và tiện lợi hơn khi phần còn lại của toolchain là JavaScript: bạn có thể pipe đầu ra của nó trực tiếp vào Node streams, import nó như thư viện, hoặc dùng API colParser cho ép kiểu theo từng cột trong code. Ưu tiên Miller cho thông lượng thô và pipeline shell; ưu tiên csvtojson khi bạn cần tích hợp chặt chẽ với ứng dụng Node.js.

Lưu ý:jq không phân tích CSV nguyên bản. Nếu bạn cần jq trong pipeline, hãy chuyển đổi sang JSON trước bằng csvtojson hoặc mlr, sau đó pipe đầu ra JSON vào jq để lọc và biến đổi.

Lựa chọn hiệu năng cao — PapaParse

Cách tiếp cận thủ công dùng split(',') thất bại với các tệp CSV trong thực tế. Các trường có dấu ngoặc kép chứa dấu phẩy, ký tự xuống dòng nhúng, dấu ngoặc kép đôi được thoát — tất cả những điều này phá vỡ bộ tách đơn giản. PapaParse là thư viện tôi dùng khi CSV đến từ nguồn không rõ. Nó xử lý mọi trường hợp đặc biệt RFC 4180, tự động phát hiện dấu phân cách, và hoạt động trong cả Node.js lẫn trình duyệt.

bash — install PapaParse
npm install papaparse
JavaScript — PapaParse with type coercion
import Papa from 'papaparse'

const csv = `product,description,price,in_stock
"Widget, Large","A premium widget with ""extra"" features",29.99,true
Bolt Assembly,Standard M8 bolt kit,4.50,true
"Gasket Set","Includes gasket, seal, and O-ring",12.75,false`

const { data, errors, meta } = Papa.parse(csv, {
  header: true,
  dynamicTyping: true,     // tự động chuyển đổi số và boolean
  skipEmptyLines: true,
  transformHeader: h => h.trim().toLowerCase().replace(/\s+/g, '_'),
})

if (errors.length > 0) {
  console.error('Parse errors:', errors)
}

console.log(JSON.stringify(data, null, 2))
// [
//   {
//     "product": "Widget, Large",
//     "description": "A premium widget with \"extra\" features",
//     "price": 29.99,
//     "in_stock": true
//   },
//   ...
// ]
console.log(`Parsed ${data.length} rows, delimiter: "${meta.delimiter}"`)

Tùy chọn dynamicTyping của PapaParse thực hiện ép kiểu tự động — số trở thành số, "true"/"false" trở thành boolean. Callback transformHeader chuẩn hóa tên cột sang snake_case, giúp bạn không phải xử lý các tiêu đề không nhất quán từ các xuất CSV khác nhau. Để chuyển đổi nhanh mà không cần viết bất kỳ code phân tích nào, công cụ chuyển đổi CSV sang JSON xử lý tất cả điều này trong trình duyệt.

Đầu ra terminal với tô sáng cú pháp

Xuất một mảng JSON lớn ra terminal sẽ khiến mắt bạn mờ đi nhanh chóng. Thêm tô sáng cú pháp vào đầu ra giúp nó dễ đọc trong quá trình debug và phát triển. Gói cli-highlight tô màu đầu ra JSON trong các terminal Node.js.

bash — install cli-highlight
npm install cli-highlight
JavaScript — colorized JSON output in terminal
import { highlight } from 'cli-highlight'

// Sau khi chuyển đổi CSV sang mảng JSON
const jsonOutput = JSON.stringify(rows, null, 2)

// In với tô sáng cú pháp
console.log(highlight(jsonOutput, { language: 'json' }))
// Khóa, chuỗi, số và boolean mỗi loại có màu riêng biệt

Đầu ra có màu sắc tỏ ra hữu ích khi bạn đang kiểm tra kết quả chuyển đổi lớn một cách tương tác. Khóa JSON, giá trị chuỗi, số và boolean mỗi loại được tô màu ANSI riêng biệt, giúp dễ dàng phát hiện trường có kiểu sai — ví dụ: một số port phải là 8080 nhưng được tô sáng như chuỗi vì ép kiểu đã tắt. Điều này đặc biệt hữu ích khi debug các tệp CSV được xuất từ công cụ bảng tính nơi kiểu cột không nhất quán giữa các hàng. Không có màu sắc, việc quét 50 hàng JSON để tìm một trường sai kiểu duy nhất đòi hỏi đọc từng giá trị một. Với màu sắc, một số được tô màu chuỗi sẽ nổi bật ngay lập tức.

Cảnh báo:Tô sáng cú pháp thêm các mã thoát ANSI vào đầu ra. Không dùng nó khi ghi JSON vào tệp, pipe sang chương trình khác, hoặc gửi làm thân phản hồi API — các mã thoát sẽ làm hỏng JSON. Chỉ dùng tô sáng để hiển thị trên terminal.

Làm việc với tệp CSV lớn

Tải tệp CSV 500 MB vào chuỗi bằng readFileSync() sẽ ngốn bộ nhớ và có thể làm sập tiến trình của bạn. Với các tệp lớn, hãy stream CSV từng dòng và phát ra các đối tượng JSON khi chúng đến. Gói csv-parse (thuộc hệ sinh thái csv trên npm) cung cấp bộ phân tích streaming hoạt động với Node.js streams.

Streaming CSV sang NDJSON với csv-parse

NDJSON (Newline-Delimited JSON) là định dạng mà mỗi dòng của tệp đầu ra là một đối tượng JSON độc lập. Không giống như một mảng JSON lớn duy nhất — yêu cầu toàn bộ tệp phải nằm trong bộ nhớ trước khi bạn có thể bắt đầu đọc — các tệp NDJSON có thể được xử lý từng dòng. Điều này làm cho NDJSON lý tưởng cho các tập dữ liệu lớn sẽ được sử dụng bởi các bộ xử lý log, pipeline luồng, hoặc cơ sở dữ liệu với API bulk-import. Gói csv-parse phát ra một đối tượng JavaScript cho mỗi hàng CSV ở chế độ object mode, vì vậy bạn có thể pipe nó trực tiếp vào transform stream thêm \n sau mỗi JSON.stringify(row).

Node.js 18+ — streaming CSV to NDJSON
import { createReadStream, createWriteStream } from 'node:fs'
import { parse } from 'csv-parse'
import { Transform } from 'node:stream'
import { pipeline } from 'node:stream/promises'

// Biến đổi mỗi đối tượng hàng CSV thành một dòng JSON
const toNdjson = new Transform({
  objectMode: true,
  transform(record, encoding, callback) {
    callback(null, JSON.stringify(record) + '\n')
  },
})

await pipeline(
  createReadStream('telemetry-2026-03.csv'),
  parse({
    columns: true,       // dùng hàng đầu tiên làm tiêu đề
    skip_empty_lines: true,
    trim: true,
    cast: true,          // tự động chuyển đổi số và boolean
  }),
  toNdjson,
  createWriteStream('telemetry-2026-03.ndjson')
)

console.log('Streaming conversion complete')
// Mỗi dòng trong tệp đầu ra là một đối tượng JSON:
// {"timestamp":"2026-03-15T08:22:00Z","service":"gateway","latency_ms":42,"status":200}
// {"timestamp":"2026-03-15T08:22:01Z","service":"auth","latency_ms":156,"status":401}
// ...

PapaParse Streaming cho trình duyệt và Node.js

Chế độ streaming của PapaParse dùng callback step được gọi một lần cho mỗi hàng thay vì thu thập tất cả hàng trong bộ nhớ. Bạn truyền cho nó một ReadStream của Node.js (trong Node.js) hoặc đối tượng File (trong trình duyệt) và PapaParse xử lý việc phân khúc nội bộ. Không cần thiết lập pipeline luồng — chỉ cần một callback. Dùng nó khi bạn cần tuân thủ RFC 4180 mà không cần đưa vào csv-parse.

Node.js — PapaParse streaming large file
import Papa from 'papaparse'
import { createReadStream } from 'node:fs'

let rowCount = 0
const errors = []

const fileStream = createReadStream('warehouse-inventory.csv')

Papa.parse(fileStream, {
  header: true,
  dynamicTyping: true,
  step(result) {
    // Xử lý từng hàng một — bộ nhớ không đổi
    rowCount++
    if (result.data.quantity === 0) {
      errors.push(`Row ${rowCount}: ${result.data.sku} is out of stock`)
    }
  },
  complete() {
    console.log(`Processed ${rowCount} rows`)
    if (errors.length > 0) {
      console.log(`Issues found: ${errors.length}`)
      errors.forEach(e => console.log(`  ${e}`))
    }
  },
  error(err) {
    console.error('Parse failed:', err.message)
  },
})
Lưu ý:Chuyển sang streaming khi tệp CSV của bạn vượt quá 50 MB hoặc khi bạn đang xử lý đầu vào không giới hạn (WebSocket feed, server-sent events, stdin qua pipe). Định dạng NDJSON (một đối tượng JSON mỗi dòng) thường là định dạng đầu ra tốt hơn mảng JSON khổng lồ cho các tập dữ liệu lớn — nó có thể được xử lý từng dòng mà không tải toàn bộ tệp vào bộ nhớ.

Các lỗi thường gặp

Gọi JSON.parse() trực tiếp trên chuỗi CSV

Vấn đề: CSV không phải JSON. Truyền chuỗi CSV thô vào JSON.parse() sẽ ném ra SyntaxError vì dấu phẩy và ký tự xuống dòng không phải cú pháp JSON hợp lệ.

Cách sửa: Phân tích CSV thành các đối tượng JavaScript trước bằng split() hoặc thư viện, sau đó dùng JSON.stringify() để tạo JSON. Chỉ gọi JSON.parse() trên các chuỗi đã là JSON hợp lệ.

Before · JavaScript
After · JavaScript
const csv = 'name,email\nNguyễn Văn An,nvan@nexuslabs.io'
const data = JSON.parse(csv)
// SyntaxError: Unexpected token 'a'
const csv = 'name,email\nNguyễn Văn An,nvan@nexuslabs.io'
const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line =>
  Object.fromEntries(headers.map((h, i) => [h, line.split(',')[i]]))
)
const json = JSON.stringify(rows, null, 2)  // chuỗi JSON hợp lệ
Dùng toString() thay vì JSON.stringify()

Vấn đề: Gọi toString() trên một đối tượng JavaScript trả về chuỗi vô dụng '[object Object]' thay vì dữ liệu thực sự. Điều này âm thầm phá hủy đầu ra CSV-to-JSON của bạn.

Cách sửa: Luôn dùng JSON.stringify() để chuyển đổi các đối tượng JavaScript thành chuỗi JSON. toString() tồn tại để ép kiểu primitive sang chuỗi, không phải để serialize.

Before · JavaScript
After · JavaScript
const row = { server: 'api-gateway', port: 8080 }
const output = row.toString()
// "[object Object]"  — dữ liệu đã mất
const row = { server: 'api-gateway', port: 8080 }
const output = JSON.stringify(row, null, 2)
// '{"server":"api-gateway","port":8080}'
Tách theo dấu phẩy mà không xử lý trường có dấu ngoặc kép

Vấn đề: Cách tách đơn giản bằng split(",") bị hỏng khi giá trị CSV chứa dấu phẩy bên trong trường có dấu ngoặc kép: "Widget, Large" trở thành hai giá trị riêng biệt thay vì một.

Cách sửa: Dùng PapaParse hoặc csv-parse cho bất kỳ dữ liệu CSV nào bạn không hoàn toàn kiểm soát. Nếu bạn phải phân tích thủ công, hãy triển khai bộ phân tích máy trạng thái theo dõi vị trí hiện tại có đang trong trường có dấu ngoặc kép hay không.

Before · JavaScript
After · JavaScript
const line = '"Widget, Large","Premium quality",29.99'
const values = line.split(',')
// ["\"Widget", " Large\"", "\"Premium quality\"", "29.99"]
// 4 giá trị thay vì 3 — trường đầu tiên bị tách sai
import Papa from 'papaparse'
const { data } = Papa.parse('"Widget, Large","Premium quality",29.99')
// data[0] = ["Widget, Large", "Premium quality", "29.99"]
// 3 giá trị, được phân tích đúng cách
Quên rằng tất cả giá trị CSV đều là chuỗi

Vấn đề: Không có ép kiểu, port: "8080" giữ nguyên là chuỗi trong JSON thay vì là số. Các hệ thống tiếp theo mong đợi kiểu số sẽ từ chối hoặc xử lý sai dữ liệu.

Cách sửa: Áp dụng ép kiểu tường minh trong bước ánh xạ, hoặc dùng PapaParse với dynamicTyping: true. Luôn chủ động xác định trường nào phải là số.

Before · JavaScript
After · JavaScript
const row = { port: '8443', debug: 'true', workers: '4' }
JSON.stringify(row)
// {"port":"8443","debug":"true","workers":"4"}  — tất cả là chuỗi
const row = {
  port: Number('8443'),           // 8443
  debug: 'true' === 'true',      // true
  workers: Number('4'),           // 4
}
JSON.stringify(row)
// {"port":8443,"debug":true,"workers":4}  — kiểu đúng

Phân tích thủ công vs thư viện — So sánh nhanh

Phương thức
Đầu ra đẹp
JSON hợp lệ
Kiểu tùy chỉnh
Streaming
Cần cài đặt
JSON.stringify()
✓ (với space)
✓ qua toJSON()/replacer
Không (tích hợp sẵn)
JSON.parse()
N/A (phân tích)
✓ (xác thực)
✓ qua reviver
Không (tích hợp sẵn)
csv-parse (Node.js)
✗ (trả về đối tượng)
✗ (không phải JSON)
npm install
PapaParse
✗ (trả về đối tượng)
✗ (không phải JSON)
npm install
csvtojson
✓ qua colParser
npm install
jq (CLI)
N/A
Cài đặt hệ thống
Miller (CLI)
N/A
Cài đặt hệ thống

Với các script nhanh mà bạn kiểm soát định dạng CSV và biết không có trường có dấu ngoặc kép, cách tiếp cận tích hợp sẵn dùng split() + JSON.stringify() hoạt động tốt và không cần phụ thuộc. Với các hệ thống sản xuất xử lý tệp CSV do người dùng tải lên, hãy dùng PapaParse trong trình duyệt hoặc csv-parse trong Node.js — cả hai đều xử lý RFC 4180 đúng cách và hỗ trợ streaming. Gói csvtojson là gói duy nhất xuất JSON trực tiếp, xử lý cả phân tích và chuyển đổi trong một lần gọi. Khi bạn cần con đường nhanh nhất từ dán-đến-kết quả, công cụ chuyển đổi CSV sang JSON xử lý tất cả trong trình duyệt mà không cần thiết lập.

Câu hỏi thường gặp

Làm thế nào để chuyển đổi CSV sang JSON trong JavaScript mà không cần thư viện?

Tách chuỗi CSV theo ký tự xuống dòng để lấy các hàng, trích xuất tiêu đề từ hàng đầu tiên bằng split(","), sau đó ánh xạ các hàng còn lại thành các đối tượng với khóa là tiêu đề. Cuối cùng dùng JSON.stringify(array, null, 2) để tạo chuỗi JSON có định dạng đẹp. Cách tiếp cận này hoạt động tốt với các tệp CSV đơn giản mà bạn kiểm soát định dạng và biết rằng không có trường có dấu ngoặc kép hay dấu phẩy bên trong. Với dữ liệu từ bảng tính xuất ra hoặc hệ thống bên thứ ba, các trường có dấu ngoặc kép và giá trị nhiều dòng sẽ làm hỏng cách tách đơn giản — hãy chuyển sang PapaParse hoặc csv-parse trong những trường hợp đó. Với các tệp rất nhỏ được xử lý trong trình duyệt, cách tiếp cận không cần thư viện này là lý tưởng.

JavaScript
const csv = `name,email,department
Nguyễn Văn An,nvan@nexuslabs.io,Engineering
Trần Thị Hoa,tthoa@nexuslabs.io,Product`

const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line => {
  const values = line.split(',')
  return Object.fromEntries(headers.map((h, i) => [h.trim(), values[i]?.trim()]))
})

console.log(JSON.stringify(rows, null, 2))
// [
//   { "name": "Nguyễn Văn An", "email": "nvan@nexuslabs.io", "department": "Engineering" },
//   { "name": "Trần Thị Hoa", "email": "tthoa@nexuslabs.io", "department": "Product" }
// ]

Sự khác biệt giữa JSON.stringify() và toString() đối với các đối tượng là gì?

Gọi toString() trên một đối tượng thông thường trả về chuỗi vô dụng "[object Object]" — không cho biết gì về dữ liệu thực sự. JSON.stringify() tạo ra một chuỗi JSON hợp lệ với tất cả các khóa và giá trị được chuyển đổi đúng cách, bao gồm cả các đối tượng và mảng lồng nhau. Luôn dùng JSON.stringify() khi bạn cần chuyển đổi một đối tượng JavaScript (như một hàng dữ liệu từ CSV) thành chuỗi JSON. Để thực hiện thao tác ngược lại — tái tạo các đối tượng JavaScript từ chuỗi JSON — dùng JSON.parse(), đây là phép nghịch đảo chính xác của JSON.stringify(). Hai hàm này tạo thành một vòng tròn hoàn chỉnh: stringify để lưu trữ hoặc truyền dữ liệu, parse để sử dụng lại.

JavaScript
const row = { name: 'Nguyễn Văn An', role: 'Engineer' }

console.log(row.toString())       // "[object Object]"
console.log(JSON.stringify(row))   // '{"name":"Nguyễn Văn An","role":"Engineer"}'

Làm thế nào để xử lý các trường CSV chứa dấu phẩy hoặc dấu ngoặc kép?

RFC 4180 quy định rằng các trường chứa dấu phẩy, ký tự xuống dòng hoặc dấu ngoặc kép đôi phải được bao bọc trong dấu ngoặc kép đôi, với dấu ngoặc kép bên trong được thoát bằng cách nhân đôi ("" trong một trường có dấu ngoặc kép đại diện cho một dấu ngoặc kép đơn thực sự). Cách tách thủ công bằng split(",") sẽ bị hỏng với các tệp này — ranh giới trường không còn là dấu phẩy đơn giản nữa. Hãy dùng PapaParse hoặc csv-parse cho dữ liệu thực tế, hoặc viết một bộ phân tích máy trạng thái theo dõi xem bạn đang ở bên trong một trường có dấu ngoặc kép hay không. Viết một máy trạng thái chính xác từ đầu là khá khó: bạn phải xử lý dấu ngoặc kép ở đầu trường, dấu ngoặc kép thoát ở giữa trường, ký tự xuống dòng bên trong trường có dấu ngoặc kép, và các quy ước kết thúc dòng khác nhau (CRLF so với LF). Với bất kỳ dữ liệu CSV nào không phải đơn giản, hãy dùng thư viện đã được kiểm thử kỹ.

JavaScript
// PapaParse xử lý RFC 4180 đúng cách
import Papa from 'papaparse'

const csv = `product,description,price
"Widget, Large","A big ""widget""",29.99
Bolt,Standard bolt,1.50`

const { data } = Papa.parse(csv, { header: true })
console.log(JSON.stringify(data, null, 2))
// trường description chứa đúng: A big "widget"

Tôi có thể chuyển đổi CSV sang JSON trực tiếp trong trình duyệt không?

Có. Cả JSON.stringify() và JSON.parse() đều được tích hợp sẵn trong mọi công cụ trình duyệt. Đối với bước phân tích CSV, bạn có thể tách theo ký tự xuống dòng và dấu phẩy cho các tệp đơn giản, hoặc đưa vào PapaParse qua CDN (không có phụ thuộc vào Node.js). Toàn bộ quá trình chuyển đổi diễn ra phía client mà không cần gửi lên server, điều này hữu ích cho việc bảo mật tệp — dữ liệu CSV thô không bao giờ rời khỏi máy của người dùng. Khi người dùng tải lên tệp CSV qua phần tử <input type="file">, phương thức file.text() của File API trả về một Promise giải quyết thành nội dung tệp dưới dạng chuỗi, mà bạn có thể truyền vào hàm chuyển đổi. Điều này làm cho việc chuyển đổi CSV-to-JSON hoàn toàn trong trình duyệt trở nên thực tế cho các dashboard, công cụ dành cho lập trình viên, và bất kỳ ứng dụng nào cần xử lý dữ liệu được tải lên mà không cần backend.

JavaScript
// Trình duyệt: người dùng tải lên tệp CSV
const fileInput = document.querySelector('input[type="file"]')
fileInput.addEventListener('change', async (event) => {
  const file = event.target.files[0]
  const text = await file.text()
  const lines = text.trim().split('\n')
  const headers = lines[0].split(',')
  const rows = lines.slice(1).map(line =>
    Object.fromEntries(headers.map((h, i) => [h.trim(), line.split(',')[i]?.trim()]))
  )
  console.log(JSON.stringify(rows, null, 2))
})

Làm thế nào để phân tích giá trị số và boolean từ CSV thay vì giữ tất cả dưới dạng chuỗi?

CSV không có hệ thống kiểu — mọi trường đều là chuỗi. Bạn phải ép kiểu giá trị trong bước ánh xạ. Kiểm tra các mẫu số bằng Number() hoặc parseFloat(), chuyển đổi "true"/"false" thành boolean, và xử lý chuỗi rỗng thành null. Hãy cẩn thận với các trường trông như số nhưng phải giữ dưới dạng chuỗi: mã bưu chính như "07302" mất số không đầu khi ép kiểu bằng Number(), và số điện thoại hoặc mã sản phẩm có ký tự số cũng dễ bị hỏng tương tự. Thư viện csvtojson thực hiện ép kiểu tự động qua tùy chọn colParser, cho phép bạn chỉ định hàm chuyển đổi theo từng cột và ghi đè tính năng tự động phát hiện cho các cột có vấn đề. Tùy chọn dynamicTyping của PapaParse áp dụng ép kiểu tương tự trên toàn bộ các cột.

JavaScript
function coerceValue(val) {
  if (val === '') return null
  if (val === 'true') return true
  if (val === 'false') return false
  const num = Number(val)
  if (!isNaN(num) && val.trim() !== '') return num
  return val
}

// Áp dụng trong quá trình ánh xạ CSV sang đối tượng
const row = { port: coerceValue('8443'), debug: coerceValue('true'), host: coerceValue('api.internal') }
// { port: 8443, debug: true, host: "api.internal" }

Làm thế nào để ghi đầu ra CSV-to-JSON vào tệp trong Node.js?

Dùng fs.writeFileSync() với đầu ra từ JSON.stringify(). Tham số thứ ba của JSON.stringify kiểm soát thụt lề — truyền 2 cho thụt lề hai dấu cách hoặc "\t" cho tab. Để đọc lại tệp, dùng JSON.parse(fs.readFileSync(path, "utf8")), giúp tái tạo mảng đối tượng JavaScript. Nếu bạn đang ghi tệp trong ngữ cảnh bất đồng bộ (bên trong hàm async hoặc ở cấp độ module ES), hãy ưu tiên fs.promises.writeFile() để tránh chặn vòng lặp sự kiện trong khi tệp đang được ghi. Với các đầu ra JSON lớn hơn vài megabyte, hãy xem xét việc pipe một JSON stream vào WriteStream thay vì xây dựng toàn bộ chuỗi trong bộ nhớ trước khi ghi.

JavaScript
import { writeFileSync, readFileSync } from 'node:fs'

// Ghi
const jsonOutput = JSON.stringify(rows, null, 2)
writeFileSync('employees.json', jsonOutput, 'utf8')

// Đọc lại
const parsed = JSON.parse(readFileSync('employees.json', 'utf8'))
console.log(Array.isArray(parsed))  // true
console.log(parsed[0].name)         // "Nguyễn Văn An"

Công cụ liên quan

Cũng có sẵn trong:Python
AC
Alex ChenFront-end & Node.js Developer

Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.

SL
Sophie LaurentNgười đánh giá kỹ thuật

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.