CSV to JSON JavaScript — Converter + Code Examples
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.
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:
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 là "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:
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.
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
// 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.
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 ISOReviver để 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:
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()) // 2024JSON.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 và space đều tùy chọn — truyền null cho replacer khi bạn chỉ cần thụt lề.
Tham số của JSON.parse():
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.
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) // 3Mộ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ố:
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.
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
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
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)
}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.
# 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));
"# 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
# 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.
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.
npm install papaparse
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.
npm install cli-highlight
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.
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).
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.
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)
},
})Các lỗi thường gặp
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ệ.
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ệ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.
const row = { server: 'api-gateway', port: 8080 }
const output = row.toString()
// "[object Object]" — dữ liệu đã mấtconst row = { server: 'api-gateway', port: 8080 }
const output = JSON.stringify(row, null, 2)
// '{"server":"api-gateway","port":8080}'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.
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 saiimport 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áchVấ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ố.
const row = { port: '8443', debug: 'true', workers: '4' }
JSON.stringify(row)
// {"port":"8443","debug":"true","workers":"4"} — tất cả là chuỗiconst row = {
port: Number('8443'), // 8443
debug: 'true' === 'true', // true
workers: Number('4'), // 4
}
JSON.stringify(row)
// {"port":8443,"debug":true,"workers":4} — kiểu đúngPhân tích thủ công vs thư viện — So sánh nhanh
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.
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.
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ỹ.
// 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.
// 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.
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.
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
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.
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.