ถอดรหัส URL ใน JavaScript — decodeURIComponent()
ใช้ ถอดรหัส URL ออนไลน์ ฟรีโดยตรงในเบราว์เซอร์ของคุณ — ไม่ต้องติดตั้ง
ลอง ถอดรหัส URL ออนไลน์ ออนไลน์ →Percent-encoded strings ปรากฏใน JavaScript code อยู่ตลอดเวลา — search query มาในรูป q=standing+desk%26price%3A200, OAuth redirect เป็น next=https%3A%2F%2Fdashboard.internal%2F, storage path เป็น reports%2F2025%2Fq1.pdf. วิธี URL decode ใน JavaScript ขึ้นอยู่กับการเลือก built-in function ที่ถูกต้องจากสามตัว: decodeURIComponent(), decodeURI() และ URLSearchParams — การเลือกระหว่างพวกมัน คือสาเหตุหลักของ silent data corruption ส่วนใหญ่ที่ฉันเคยเห็นใน production codebases โดยเฉพาะ edge case +-เป็น-ช่องว่าง และ double-decoding สำหรับการ decode เร็วโดยไม่ต้องเขียน code, URL Decoder ของ ToolDeck จัดการได้ทันทีในเบราว์เซอร์ บทช่วยสอน JavaScript URL decoding นี้ครอบคลุมทั้งสาม function อย่างละเอียด (ES2015+ / Node.js 10+): ควรใช้แต่ละอันเมื่อใด ความแตกต่างสำหรับ ช่องว่างและ reserved characters การ decode จาก files และ HTTP requests การจัดการ error อย่างปลอดภัย และสี่ข้อผิดพลาดที่ทำให้เกิด production bugs ที่ละเอียดอ่อนที่สุด
- ✓decodeURIComponent() ถอดรหัสทุก percent-encoded sequences — เป็นตัวเลือกที่ถูกต้องสำหรับค่า query parameter แต่ละตัวและ path segments
- ✓decodeURI() รักษา structural URI characters เช่น / ? & = # : ไว้ — ใช้เฉพาะเมื่อถอดรหัส URL string ทั้งหมด ไม่ใช่สำหรับค่าแต่ละตัว
- ✓URLSearchParams.get() คืนค่าที่ถอดรหัสแล้ว — การเรียก decodeURIComponent() ซ้ำอีกครั้งจะทำให้เกิด double-decoding
- ✓decodeURIComponent() ไม่ถอดรหัส + เป็นช่องว่าง — สำหรับข้อมูล form-encoded (application/x-www-form-urlencoded) ให้ใช้ URLSearchParams หรือแทนที่ + ก่อนถอดรหัส
- ✓ครอบ decodeURIComponent() ด้วย try/catch เสมอเมื่อ input มาจากข้อมูลผู้ใช้ — ตัวอักษร % เดี่ยวหรือ sequence ที่ไม่สมบูรณ์จะ throw URIError
URL Decoding คืออะไร?
Percent-encoding (กำหนดอย่างเป็นทางการใน RFC 3986) แทนที่ตัวอักษรที่ไม่ปลอดภัยหรือมีความหมายเชิงโครงสร้างใน URL ด้วยเครื่องหมาย % ตามด้วยสองหลักเลขฐานสิบหก — ค่า byte UTF-8 ของตัวอักษรนั้น URL decoding ย้อนกลับการแปลงนี้: แต่ละ sequence %XX จะถูกแปลงกลับเป็น byte ต้นฉบับ และ byte sequence ที่ได้จะถูกตีความเป็น UTF-8 text ช่องว่างถอดรหัสจาก %20 เครื่องหมายทับจาก %2F ตัวอักษร non-ASCII ü จาก %C3%BC (การแทน UTF-8 สองไบต์ของมัน)
ตัวอักษรที่ไม่เคยถูก encode — เรียกว่า ตัวอักษรที่ไม่ถูกสงวนไว้ — คือตัวอักษร A–Z และ a–z ตัวเลข 0–9 และ - _ . ~ ส่วนที่เหลือทั้งหมดมีบทบาท เชิงโครงสร้างใน URL (เช่น / แบ่ง path segments หรือ & แบ่ง query parameters) หรือต้อง encode เมื่อใช้เป็นข้อมูล ผลในทางปฏิบัติ: search filter เช่น status=active&tier=premium ที่ encode เป็น query parameter value เดียวจะมีหน้าตาแตกต่างจากต้นฉบับโดยสิ้นเชิง
// Percent-encoded — ที่รับมาใน HTTP request หรือ webhook payload "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// หลัง URL decoding — Elasticsearch query filter ต้นฉบับ "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
decodeURIComponent() — Function มาตรฐานสำหรับถอดรหัส Values
decodeURIComponent() คือกำลังหลักของ URL decoding ใน JavaScript มันถอดรหัสทุก sequence %XX ใน input string — รวมถึงตัวอักษรที่ มีความหมายเชิงโครงสร้างใน URL เช่น %2F (slash), %3F (เครื่องหมายคำถาม), %26 (เครื่องหมาย &) และ %3D (เครื่องหมายเท่ากับ) ทำให้มันเป็น ตัวเลือกที่ถูกต้องสำหรับถอดรหัส query parameter values และ path segments แต่ละตัว แต่เป็นตัวเลือกที่ผิดสำหรับถอดรหัส URL ทั้งหมด — ที่ซึ่ง structural characters เหล่านั้น ต้องคง encoded ไว้ นี่คือ global function: ไม่ต้อง import ในสภาพแวดล้อม JavaScript ใดๆ
ตัวอย่างขั้นต่ำที่ใช้งานได้
// ถอดรหัส query parameter values แต่ละตัว — กรณีที่พบบ่อย
const city = decodeURIComponent('%E0%B8%81%E0%B8%A3%E0%B8%B8%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%9E%E0%B8%AF') // 'กรุงเทพฯ'
const district = decodeURIComponent('%E0%B8%AA%E0%B8%B5%E0%B8%A5%E0%B8%A1') // 'สีลม'
const category = decodeURIComponent('office%20furniture') // 'office furniture'
const filter = decodeURIComponent('price%3A%5B200+TO+800%5D') // 'price:[200+TO+800]'
// หมายเหตุ: + ไม่ถูกถอดรหัสเป็นช่องว่าง — ดูส่วน + ในตาราง comparison
console.log(city) // กรุงเทพฯ
console.log(district) // สีลม
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]ถอดรหัส redirect URL ที่ extract จาก query parameter
// Redirect URL ถูก percent-encode เมื่อมันถูก embed เป็น parameter value
// ฝั่งรับ: extract แล้วใช้ — ไม่ต้อง manual decode กับ URLSearchParams
const incomingUrl = 'https://auth.company.com/callback' +
'?next=https%3A%2F%2Fdashboard.internal%2Freports%3Fview%3Dweekly%26team%3Dplatform' +
'&session_id=sid_7x9p2k'
const url = new URL(incomingUrl)
const rawNext = url.searchParams.get('next') // Auto-decoded โดย URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext ถูก decode แล้ว: 'https://dashboard.internal/reports?view=weekly&team=platform'
// อย่าเรียก decodeURIComponent(rawNext) อีก — นั่นจะเป็น double-decoding
const nextUrl = new URL(rawNext!)
console.log(nextUrl.hostname) // dashboard.internal
console.log(nextUrl.searchParams.get('view')) // weekly
console.log(nextUrl.searchParams.get('team')) // platformถอดรหัส non-ASCII และ Unicode path segments
// REST API ที่มี internationalised path segments
// แต่ละ UTF-8 byte ของตัวอักษรต้นฉบับถูก percent-encode แยกกัน
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (Tokyo) — 3 bytes ต่อตัวอักษร
'M%C3%BCnchen', // München — ü encoded เป็น 2 bytes
'caf%C3%A9', // café — é เป็น precomposed NFC
'%E0%B8%A0%E0%B8%B9%E0%B9%80%E0%B8%81%E0%B9%87%E0%B8%95', // ภูเก็ต
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// ภูเก็ต
// Extract file key จาก storage API URL
// Object key มี / ดังนั้นจึงถูก encode เป็น %2F ภายใน path segment
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname decode URL-level encoding แต่ %2F (ในรูป %252F ที่ URL level) คงอยู่
// ใช้ decodeURIComponent สำหรับขั้นตอนสุดท้าย:
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() throw URIError เมื่อ input มี % ที่ไม่ตามด้วยสองหลักเลขฐานสิบหกที่ถูกต้อง — ตัวอย่างเช่น % เดี่ยวๆ ที่ท้าย string หรือ sequence เช่น %GH ครอบด้วย try/catch เสมอเมื่อ decode input ที่ผู้ใช้ป้อนมา Pattern การ decode อย่างปลอดภัยอธิบายไว้ในส่วน Error Handling ด้านล่างฟังก์ชัน URL Decoding ของ JavaScript — ตารางอ้างอิงตัวอักษร
Built-in decoding functions ทั้งสามแตกต่างกันในการ decode sequences ที่ต่างกัน ตารางแสดง พฤติกรรมสำหรับตัวอักษรที่สำคัญที่สุดในทางปฏิบัติ:
| ที่ encode แล้ว | ตัวอักษร | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | ช่องว่าง | ช่องว่าง ✅ | ช่องว่าง ✅ | ช่องว่าง ✅ |
| + | plus (form) | + (คงไว้) | + (คงไว้) | ช่องว่าง ✅ |
| %2B | + literal | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (คงไว้) ❌ | & ✅ |
| %3D | = | = ✅ | = (คงไว้) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (คงไว้) ❌ | ? ✅ |
| %23 | # | # ✅ | # (คงไว้) ❌ | # ✅ |
| %2F | / | / ✅ | / (คงไว้) ❌ | / ✅ |
| %3A | : | : ✅ | : (คงไว้) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (คงไว้) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
สองแถวสำคัญคือ + และ structural characters (%26, %3D, %3F). URLSearchParams decode + เป็นช่องว่างเพราะปฏิบัติตาม specification application/x-www-form-urlencoded — ถูกต้องสำหรับการส่ง HTML form แต่แตกต่างจากสิ่งที่ decodeURIComponent() ทำกับตัวอักษรเดียวกัน และ decodeURI() ข้าม %26, %3D และ %3F อย่างเงียบๆ — ซึ่งดูถูกต้องจนกว่าค่า จะมี encoded ampersand หรือ equals sign จริงๆ
decodeURI() — ถอดรหัส URL ทั้งหมดโดยไม่ทำลายโครงสร้าง
decodeURI() คือคู่ตรงข้ามของ encodeURI() มันถอดรหัส URL string ทั้งหมด ในขณะที่รักษาตัวอักษรที่มีความหมายเชิงโครงสร้างใน URI ไว้: ; , / ? : @ & = + $ # ตัวอักษรเหล่านี้ ถูกปล่อยไว้ในรูป percent-encoded (หรือเป็น literal characters ถ้าปรากฏแบบไม่ encode ใน input) ทำให้ decodeURI() ปลอดภัยสำหรับการทำความสะอาด full URLs ที่อาจมี non-ASCII characters ใน path หรือ hostname โดยไม่เผลอทำลาย query string structure
ทำความสะอาด URL ที่มี non-ASCII path segments
// CDN URL ที่มี internationalised path segments และ structured query string // decodeURI() decode non-ASCII path แต่รักษา ? & = ไว้ const encodedUrl = 'https://cdn.example.com/assets/%E6%9D%B1%E4%BA%AC%2F2025%2Fq1-report.pdf' + '?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' const readable = decodeURI(encodedUrl) console.log(readable) // https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600 // ↑ Non-ASCII decoded; ? & = preserved — URL ยังคง structurally valid // decodeURIComponent จะทำลาย URL — : / ? & = ถูก decode พร้อมกันหมด const broken = decodeURIComponent(encodedUrl) // ดูเหมือนกันที่นี่ แต่ URL อย่าง 'https%3A%2F%2F...' จะถูกทำลาย
URL constructor แทน decodeURI() เมื่อคุณต้องการเข้าถึง URL components แต่ละอันด้วย new URL(str) normalise input ตรวจสอบ structure และ expose .pathname, .searchParams และ .hostname เป็น properties ที่ decode แล้ว สำรอง decodeURI() ไว้สำหรับกรณีที่คุณต้องการผลลัพธ์เป็น string เท่านั้นและไม่สามารถใช้ URL constructor ได้ (ตัวอย่างเช่น ใน Node.js 6 environments เก่ามากที่ไม่มี global URL class)URLSearchParams — การ Decode อัตโนมัติสำหรับ Query Strings
URLSearchParams คือวิธีที่ถูกต้องตาม แบบแผนในการ parse query strings ใน JavaScript ยุคใหม่ ทุกค่าที่ return โดย .get(), .getAll() หรือการวนซ้ำจะถูก decode อัตโนมัติ — รวมถึง + เป็นช่องว่างสำหรับข้อมูล form-encoded มีให้ใช้ globally ในทุก browser ยุคใหม่และ Node.js 10+ ไม่ต้อง import และจัดการ edge cases ที่การแบ่ง string แบบ manual มักทำผิด
การ parse query string ที่เข้ามา
// Parsing webhook callback หรือ OAuth redirect query string
const rawSearch =
'?event_id=evt_9c2f4a1b' +
'&product_name=Standing+Desk+Pro' +
'&filter=price%3A%5B200+TO+800%5D' +
'&tag=ergonomic&tag=adjustable' +
'&redirect=https%3A%2F%2Fdashboard.internal%2Forders%3Fview%3Dpending'
const params = new URLSearchParams(rawSearch)
console.log(params.get('event_id')) // 'evt_9c2f4a1b'
console.log(params.get('product_name')) // 'Standing Desk Pro' ← + decoded เป็นช่องว่าง
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A และ %5B decoded
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// วนซ้ำทุก parameters
for (const [key, value] of params) {
console.log(`${key}: ${value}`)
}
// event_id: evt_9c2f4a1b
// product_name: Standing Desk Pro
// filter: price:[200+TO+800]
// tag: ergonomic
// tag: adjustable
// redirect: https://dashboard.internal/orders?view=pendingการ parse query parameters จาก URL ปัจจุบันของ browser
// URL ปัจจุบัน: https://app.example.com/search
// ?q=standing+desk
// &category=office+furniture
// &sort=price_asc
// &page=2
interface SearchFilters {
query: string | null
category: string | null
sort: string
page: number
}
function getSearchFilters(): SearchFilters {
const params = new URLSearchParams(window.location.search)
return {
query: params.get('q'), // 'standing desk'
category: params.get('category'), // 'office furniture'
sort: params.get('sort') ?? 'relevance',
page: Number(params.get('page') ?? '1'),
}
}
const filters = getSearchFilters()
console.log(filters.query) // standing desk (+ decoded อัตโนมัติ)
console.log(filters.category) // office furnitureการ Decode ข้อมูล URL-Encoded จาก Files และ API Responses
สองสถานการณ์ที่เกิดขึ้นอยู่เสมอในโปรเจกต์จริง: การประมวลผล file บน disk ที่มีข้อมูล percent-encoded (access logs, data exports, webhook capture files) และการ parse URL ของ HTTP request ที่เข้ามาใน Node.js server ทั้งสองทำตามหลักการเดียวกัน — ใช้ URL constructor หรือ URLSearchParams แทนการแบ่ง string แบบ manual — แต่รายละเอียดแตกต่างกัน
การอ่านและ decode บันทึก URL-encoded จาก file
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// File: orders-export.txt — หนึ่ง URL-encoded record ต่อบรรทัด
// customer_name=%E0%B8%AA%E0%B8%A1%E0%B8%8A%E0%B8%B2%E0%B8%A2+%E0%B9%83%E0%B8%88%E0%B8%94%E0%B8%B5&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=5390%20THB
// customer_name=%E0%B8%99%E0%B8%A0%E0%B8%B2+%E0%B8%A7%E0%B8%87%E0%B8%A8%E0%B9%8C%E0%B8%97%E0%B8%AD%E0%B8%87&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=3290%20THB
// customer_name=Somchai+Wongsawat&order_id=ord_2e8d5f&product=Monitor+Arm&total=1890%20THB
interface Order {
customerName: string | null
orderId: string | null
product: string | null
total: string | null
}
async function parseOrdersFile(filePath: string): Promise<Order[]> {
const fileStream = createReadStream(filePath, { encoding: 'utf-8' })
const rl = createInterface({ input: fileStream, crlfDelay: Infinity })
const orders: Order[] = []
for await (const line of rl) {
if (!line.trim()) continue
// URLSearchParams decode + เป็นช่องว่างและ %XX sequences อัตโนมัติ
const params = new URLSearchParams(line)
orders.push({
customerName: params.get('customer_name'), // 'สมชาย ใจดี'
orderId: params.get('order_id'), // 'ord_9c2f4a'
product: params.get('product'), // 'Standing Desk Pro'
total: params.get('total'), // '5390 THB'
})
}
return orders
}
const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'สมชาย ใจดี', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '5390 THB' }การ parse Nginx access log เพื่อ decode search queries
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// บรรทัด Nginx access log มีลักษณะดังนี้:
// 192.168.1.42 - - [11/Mar/2026:10:23:01 +0000] "GET /api/search?q=standing%20desk%26brand%3ANorthwood&sort=price_asc HTTP/1.1" 200 1842
async function extractSearchQueries(logFile: string): Promise<string[]> {
const rl = createInterface({ input: createReadStream(logFile), crlfDelay: Infinity })
const queries: string[] = []
for await (const line of rl) {
// Extract request path จาก log line
const match = line.match(/"GET ([^ ]+) HTTP/)
if (!match) continue
try {
const requestUrl = new URL(match[1], 'http://localhost')
const query = requestUrl.searchParams.get('q')
if (query) queries.push(query) // URLSearchParams decode อัตโนมัติ
} catch {
// ข้ามบรรทัดที่ไม่ถูกต้อง — access logs อาจมี entries ที่ถูกตัด
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']การ parse query parameters ใน Node.js HTTP server
import http from 'http'
// URL ที่เข้ามา: /api/products?q=standing+desk&warehouse=bangkok&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D
const server = http.createServer((req, res) => {
// สร้าง full URL — อาร์กิวเมนต์ที่สองคือ base ที่ URL constructor ต้องการ
const requestUrl = new URL(req.url!, 'http://localhost')
const searchQuery = requestUrl.searchParams.get('q') // 'standing desk'
const warehouseId = requestUrl.searchParams.get('warehouse') // 'bangkok'
const minStock = Number(requestUrl.searchParams.get('minStock') ?? '0')
const cursor = requestUrl.searchParams.get('cursor') // 'eyJpZCI6MTIzfQ=='
if (!warehouseId) {
res.writeHead(400, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'จำเป็นต้องมี parameter warehouse' }))
return
}
const cursorData = cursor ? JSON.parse(Buffer.from(cursor, 'base64').toString()) : null
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ searchQuery, warehouseId, minStock, cursorData }))
})
server.listen(3000)เมื่อคุณต้องการตรวจสอบ encoded URL ในระหว่างการพัฒนา — เพื่อเข้าใจว่า webhook ส่งอะไรมาก่อนเขียน parsing code — วางลงใน URL Decoder ของ ToolDeck โดยตรงเพื่อดู decoded form ทันทีโดยไม่ต้องรัน script
URL Decoding ผ่าน Command Line
สำหรับ shell scripts, CI pipelines หรือการตรวจสอบ encoded strings แบบ quick one-off หลายวิธีทำงานได้โดยไม่ต้องเขียน script เต็มรูปแบบ Node.js one-liners เป็น cross-platform; บน macOS และ Linux python3 ก็มีให้ใช้เสมอ
# ── Node.js one-liners ──────────────────────────────────────────────────
# Decode ค่า percent-encoded หนึ่งค่า
node -e "console.log(decodeURIComponent(process.argv[1]))" "%E0%B8%81%E0%B8%A3%E0%B8%B8%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%9E%E0%B8%AF%20%26%20%E0%B9%80%E0%B8%8A%E0%B8%B5%E0%B8%A2%E0%B8%87%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88"
# กรุงเทพฯ & เชียงใหม่
# Parse query string และ print แต่ละคู่ key=value (ที่ decode แล้ว)
node -e "
const params = new URLSearchParams(process.argv[1])
for (const [k, v] of params) console.log(`${k} = ${v}`)
" "q=standing+desk&warehouse=phuket&minStock=10"
# q = standing desk
# warehouse = phuket
# minStock = 10
# Decode และ pretty-print URL-encoded JSON body (พบบ่อยใน webhook debugging)
node -e "
const raw = decodeURIComponent(process.argv[1])
console.log(JSON.stringify(JSON.parse(raw), null, 2))
" '%7B%22event%22%3A%22purchase%22%2C%22amount%22%3A149.99%2C%22currency%22%3A%22THB%22%7D'
# {
# "event": "purchase",
# "amount": 149.99,
# "currency": "THB"
# }
# ── Python one-liner (มีในระบบ macOS/Linux ส่วนใหญ่) ─────────────
# unquote_plus decode + เป็นช่องว่างด้วย — ถูกต้องสำหรับ form-encoded data
python3 -c "
from urllib.parse import unquote_plus
import sys
print(unquote_plus(sys.argv[1]))
" "Standing+Desk+%26+Ergonomic+Chair"
# Standing Desk & Ergonomic Chair
# ── curl — log และ decode redirect URL จาก response header ──────────
curl -sI "https://api.example.com/short/abc123" | grep -i location | node -e "
const line = require('fs').readFileSync('/dev/stdin', 'utf8')
const url = line.replace(/^location:s*/i, '').trim()
console.log(decodeURIComponent(url))
"Graceful Decoding ด้วย decode-uri-component
Built-in decodeURIComponent() throw URIError กับทุก sequence ที่ไม่ถูกต้อง — รวมถึง % ที่ท้ายโดยไม่มีสอง hex digits ตามมา ใน production code ที่ประมวลผล URLs จากผู้ใช้หรือ third-party — search query logs, clickthrough URLs จาก email campaigns, scraped web data — sequences ที่ไม่ถูกต้องพบได้ บ่อยพอที่จะทำให้เกิด crashes decode-uri-component package (~30M weekly npm downloads) จัดการ sequences ที่ไม่ถูกต้องอย่างนุ่มนวลโดย return sequence เดิมที่ไม่ เปลี่ยนแปลงแทนที่จะ throw
npm install decode-uri-component # หรือ pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// Native function throw บน input ที่ไม่ถูกต้อง — อาจทำให้ request handler crash
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('Native throw:', (e as Error).message)
}
// decode-uri-component return raw sequence แทนการ throw
console.log(decodeUriComponent('product%name%'))
// product%name% ← malformed sequences ถูกเก็บไว้ as-is ไม่ throw
// Valid sequences ถูก decode ถูกต้อง
console.log(decodeUriComponent('%E0%B8%81%E0%B8%A3%E0%B8%B8%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%9E%E0%B8%AF%20%26%20%E0%B8%A0%E0%B8%B9%E0%B9%80%E0%B8%81%E0%B9%87%E0%B8%95'))
// กรุงเทพฯ & ภูเก็ต
// ผสม: valid sequences ถูก decode, invalid ถูกเก็บไว้
console.log(decodeUriComponent('%E0%B9%80%E0%B8%8A%E0%B8%B5%E0%B8%A2%E0%B8%87%E0%B9%83%E0%B8%AB%E0%B8%A1%E0%B9%88%20%ZZ%20Region'))
// เชียงใหม่ %ZZ Region ← %ZZ ไม่ใช่ hex ที่ถูกต้อง — ถูกเก็บไว้ as-is
// การใช้งานจริงใน log processing pipeline
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← จะ throw กับ decodeURIComponent (bare %)
'wireless%20keyboard%20%26%20mouse',
]
const decoded = rawSearchQueries.map(q => decodeUriComponent(q))
console.log(decoded)
// ['standing desk ergonomic', 'price:[200+TO+800]', '50%25+off', 'wireless keyboard & mouse']ใช้ decodeURIComponent() กับ try/catch สำหรับ application code ที่ คุณต้องการรู้ทันทีหาก input ที่ไม่คาดคิดมาถึง ใช้ decode-uri-component ใน data pipelines, log processors และ webhook handlers ที่คุณต้องการประมวลผลต่อไปแม้ว่า input บางตัวจะมี encoding ที่ไม่ถูกต้อง
การจัดการ Percent-Encoded Strings ที่ไม่ถูกต้อง
ตัวอักษร % เดี่ยว, sequence ที่ไม่สมบูรณ์ เช่น %A หรือคู่ที่ไม่ถูกต้องเช่น %GH ทั้งหมดทำให้ decodeURIComponent() throw URIError: URI malformed Input ใดๆ ที่ผู้ใช้ ควบคุม — search queries, URL fragments, form fields ที่มี URLs, parameters จาก email campaign links — อาจมี sequences เหล่านี้ Safe wrapper เป็นสิ่งจำเป็นสำหรับ code ที่ เผชิญกับภายนอก
Safe decode wrappers สำหรับสถานการณ์ทั่วไป
// ── 1. Safe decode พื้นฐาน — return string ต้นฉบับเมื่อเกิด error ─────────
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // return raw input หาก decode ล้มเหลว — ไม่ crash
}
}
// ── 2. Safe decode + จัดการ + เป็นช่องว่าง (สำหรับ form-encoded values) ─────────
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // อย่างน้อย replace + แม้ส่วนที่เหลือจะล้มเหลว
}
}
// ── 3. Safe full query string parser → plain object ──────────────────────
function parseQueryString(queryString: string): Record<string, string> {
const result: Record<string, string> = {}
try {
const params = new URLSearchParams(queryString)
for (const [key, value] of params) {
result[key] = value // URLSearchParams จัดการ decoding ทั้งหมดภายใน
}
} catch {
// Return ว่างเปล่าอย่างเงียบๆ เมื่อ input ไม่ถูกต้องสมบูรณ์
}
return result
}
// การใช้งาน
console.log(safeDecode('%E0%B8%81%E0%B8%A3%E0%B8%B8%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%9E%E0%B8%AF')) // กรุงเทพฯ
console.log(safeDecode('search%20for%2050%25+off')) // search for 50% off (bare %)
// → จริงๆ แล้วใช้ได้; % ที่นี่คือ %25
console.log(safeDecode('malformed%string%')) // malformed%string% (ไม่ throw)
console.log(safeFormDecode('standing+desk+pro')) // standing desk pro
console.log(parseQueryString('q=hello+world&tag=node%20js&page=2'))
// { q: 'hello world', tag: 'node js', page: '2' }ข้อผิดพลาดทั่วไป
ฉันเคยเห็น pattern สี่อย่างนี้ทำให้เกิด silent data corruption หรือ crash ที่ไม่คาดคิด ใน production — โดยปกติเฉพาะเมื่อค่ามีตัวอักษรพิเศษซึ่งหมายความว่ามันผ่าน unit tests และปรากฏขึ้นพร้อมข้อมูลผู้ใช้จริง
ข้อผิดพลาด 1 — Double-decode ค่าจาก URLSearchParams
ปัญหา: URLSearchParams.get() return string ที่ decode แล้ว การเรียก decodeURIComponent() ซ้ำอีกครั้งจะ double-decode ค่า — เปลี่ยน % ที่เหลืออยู่ใน decoded output ให้กลาย เป็น %25 ซึ่งทำให้ข้อมูลเสียหาย การแก้ไข: ใช้ค่าจาก URLSearchParams.get() โดยตรง — ไม่จำเป็น ต้อง decode เพิ่มเติม
// ❌ URLSearchParams decode แล้ว — decode ซ้ำทำให้ค่าที่มี % เสียหาย
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← decode แล้ว
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← % ถูก decode เป็น %25 ในครั้งที่สอง — ผิดอีกครั้ง// ✅ ใช้ URLSearchParams.get() โดยตรง — decoding อัตโนมัติ
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate = qs.get('rate') // '50%' ← ถูกต้อง ไม่ต้องมีขั้นตอนเพิ่ม
const redirect = qs.get('redirect') // 'https://dashboard.internal/'
const parsed = new URL(redirect!) // ปลอดภัยในการสร้าง — decode แล้ว
console.log(parsed.hostname) // dashboard.internalข้อผิดพลาด 2 — ไม่ decode + เป็นช่องว่างสำหรับข้อมูล form-encoded
ปัญหา: การส่ง form และ OAuth libraries บางตัว encode ช่องว่างเป็น + (application/x-www-form-urlencoded) การเรียก decodeURIComponent() บนข้อมูลนี้จะเก็บ + ไว้เป็นเครื่องหมายบวก literal การแก้ไข: ใช้ URLSearchParams เพื่อ parse ข้อมูล form-encoded หรือแทนที่ + ด้วยช่องว่างก่อนเรียก decodeURIComponent()
// ❌ decodeURIComponent ถือว่า + เป็นเครื่องหมายบวก literal ไม่ใช่ช่องว่าง
// OAuth token endpoint ส่ง: grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// OAuth implementations เก่าบางตัวใช้ + สำหรับช่องว่างใน codes ด้วย
const formBody = 'customer_name=%E0%B8%AA%E0%B8%A1%E0%B8%8A%E0%B8%B2%E0%B8%A2+%E0%B9%83%E0%B8%88%E0%B8%94%E0%B8%B5&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // 'สมชาย+ใจดี' ← + ไม่ถูกแปลงเป็นช่องว่าง// ✅ ใช้ URLSearchParams — ปฏิบัติตาม spec application/x-www-form-urlencoded
const formBody = 'customer_name=%E0%B8%AA%E0%B8%A1%E0%B8%8A%E0%B8%B2%E0%B8%A2+%E0%B9%83%E0%B8%88%E0%B8%94%E0%B8%B5&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'สมชาย ใจดี' ← + decode ถูกต้องเป็นช่องว่าง
console.log(params.get('product')) // 'Standing Desk Pro'ข้อผิดพลาด 3 — ใช้ decodeURI() สำหรับค่า query parameter แต่ละตัว
ปัญหา: decodeURI() ไม่ decode %26 (&), %3D (=) หรือ %3F (?) — ตัวอักษรที่มักถูก encode ภายใน parameter values การใช้เพื่อ decode ค่าเดียวจะทิ้ง sequences เหล่านั้นไว้ครบถ้วน ทำให้ output ผิดอย่างเงียบๆ การแก้ไข: ใช้ decodeURIComponent() สำหรับค่าแต่ละตัว; สำรอง decodeURI() ไว้สำหรับ URL strings ทั้งหมด
// ❌ decodeURI ไม่ decode & และ = — ค่ายังคงเสีย const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURI(encodedFilter) console.log(filter) // 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' ← %3D และ %26 ไม่ถูก decode
// ✅ decodeURIComponent decode ทุก sequences รวมถึง & และ = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← decode ถูกต้อง
ข้อผิดพลาด 4 — ไม่ครอบ decodeURIComponent ใน try/catch สำหรับ input จากผู้ใช้
ปัญหา: ตัวอักษร %เดี่ยวที่ไม่ตามด้วยสอง hex digits — พบบ่อยใน search queries ที่ผู้ใช้พิมพ์ (“50% off”, “100% cotton”) — ทำให้ decodeURIComponent() throw URIError: URI malformed ทำให้ request handler crash หากไม่ถูก catch การแก้ไข: ครอบใน try/catch เสมอเมื่อ input มาจากข้อมูล ผู้ใช้ URL fragments หรือระบบภายนอก
// ❌ ผู้ใช้พิมพ์ '100% cotton' ในช่องค้นหา — bare % ทำให้ server crash
// GET /api/search?q=100%25+cotton ← กรณีนี้โดยเฉพาะไม่เป็นไร (%25 = %)
// GET /api/search?q=100%+cotton ← กรณีนี้ crash (% ไม่ตามด้วย 2 hex digits)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ throw URIError: URI malformed สำหรับ '100% cotton' → unhandled 500 error
})// ✅ ครอบใน try/catch — fallback เป็น raw input หาก decode ล้มเหลว
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // ใช้ raw value แทนการ crash
}
// ประมวลผลต่ออย่างปลอดภัย — query เป็นค่าที่ decode แล้วหรือ raw
})decodeURIComponent vs decodeURI vs URLSearchParams — การเปรียบเทียบรวดเร็ว
| วิธี | Decode %XX | Decode + เป็นช่องว่าง | Throw เมื่อไม่ถูกต้อง | Decode & = ? # | กรณีใช้งาน | ต้องติดตั้ง |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ ทั้งหมด | ❌ ไม่ | ✅ URIError | ✅ ใช่ | Values แต่ละตัวและ path segments | No |
| decodeURI() | ✅ ส่วนใหญ่ | ❌ ไม่ | ✅ URIError | ❌ ไม่ | URL strings ทั้งหมด | No |
| URLSearchParams | ✅ ทั้งหมด | ✅ ใช่ | ❌ เงียบๆ | ✅ ใช่ | การ parse query string พร้อม decode อัตโนมัติ | No |
| URL constructor | ✅ ทั้งหมด | ✅ ใช่ | ✅ TypeError | ✅ ใช่ | การ parse และ normalise URL ทั้งหมด | No |
| decode-uri-component | ✅ ทั้งหมด | ❌ ไม่ | ❌ เงียบๆ | ✅ ใช่ | Batch decode รองรับ input ที่ไม่ถูกต้อง | npm install |
| querystring.unescape() | ✅ ทั้งหมด | ❌ ไม่ | ❌ เงียบๆ | ✅ ใช่ | Node.js เก่า (deprecated ใน v16) | No (built-in) |
สำหรับกรณีส่วนใหญ่ ตัวเลือกลดลงเหลือสามสถานการณ์ ใช้ URLSearchParams เพื่อ parse query strings — จัดการ decoding กฎ +-เป็น-ช่องว่าง และ repeated keys อัตโนมัติ ใช้ decodeURIComponent() (ครอบใน try/catch) สำหรับค่าเดียวหรือ path segment โดยเฉพาะเมื่อคุณคาดหวัง slashes ที่ encode เป็น %2F ภายใน segment ใช้ decodeURI() เฉพาะเมื่อคุณมี URL string ทั้งหมดที่มี non-ASCII characters ใน path และต้องการให้ structural characters (/ ? & =) ยังคง encoded ไว้ใน output
คำถามที่พบบ่อย
เครื่องมือที่เกี่ยวข้อง
สำหรับการ decode ด้วยคลิกเดียวโดยไม่ต้องเขียน code ให้วาง percent-encoded string ของคุณ ลงใน URL Decoder ของ ToolDeck โดยตรง — มัน decode ทันทีในเบราว์เซอร์ พร้อมผลลัพธ์สำหรับคัดลอกไปใน code หรือ terminal ของคุณ
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.
Marcus specialises in JavaScript performance, build tooling, and the inner workings of the V8 engine. He has spent years profiling and optimising React applications, working on bundler configurations, and squeezing every millisecond out of critical rendering paths. He writes about Core Web Vitals, JavaScript memory management, and the tools developers reach for when performance really matters.