ToolDeck

ถอดรหัส URL ใน JavaScript — decodeURIComponent()

·Front-end & Node.js Developer·ตรวจสอบโดยMarcus Webb·เผยแพร่เมื่อ

ใช้ ถอดรหัส 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 เดียวจะมีหน้าตาแตกต่างจากต้นฉบับโดยสิ้นเชิง

Before · text
After · text
// 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 ใดๆ

ตัวอย่างขั้นต่ำที่ใช้งานได้

JavaScript (browser / Node.js)
// ถอดรหัส 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

JavaScript
// 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

JavaScript
// 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

JavaScript
// 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 ที่เข้ามา

JavaScript (browser / Node.js 10+)
// 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

JavaScript (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

JavaScript (Node.js 10+)
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

JavaScript (Node.js)
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

JavaScript (Node.js 10+)
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 ก็มีให้ใช้เสมอ

bash
# ── 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

bash
npm install decode-uri-component
# หรือ
pnpm add decode-uri-component
JavaScript (Node.js)
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 สำหรับสถานการณ์ทั่วไป

JavaScript
// ── 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 เพิ่มเติม

Before · JavaScript
After · JavaScript
// ❌ 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()

Before · JavaScript
After · JavaScript
// ❌ 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 ทั้งหมด

Before · JavaScript
After · JavaScript
// ❌ 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&region=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 หรือระบบภายนอก

Before · JavaScript
After · JavaScript
// ❌ ผู้ใช้พิมพ์ '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 %XXDecode + เป็นช่องว่างThrow เมื่อไม่ถูกต้องDecode & = ? #กรณีใช้งานต้องติดตั้ง
decodeURIComponent()✅ ทั้งหมด❌ ไม่✅ URIError✅ ใช่Values แต่ละตัวและ path segmentsNo
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

คำถามที่พบบ่อย

ความแตกต่างระหว่าง decodeURIComponent() และ decodeURI() ใน JavaScript คืออะไร?
decodeURIComponent() decode ทุก percent-encoded sequence ใน string รวมถึงตัวอักษรที่มีความหมายเชิงโครงสร้างใน URL — & (%26), = (%3D), ? (%3F), # (%23), / (%2F) และ : (%3A) ออกแบบมาสำหรับการ decode ค่า query parameter แต่ละตัวหรือ path segments decodeURI() รักษา structural characters เหล่านั้น — ไม่ decode %26, %3D, %3F, %23, %2F หรือ %3A — เพราะออกแบบมาเพื่อทำความสะอาด URL ทั้งหมดโดยไม่ทำลาย query string structure การใช้ decodeURI() บน parameter value เดียวที่มี encoded & หรือ = จะเงียบๆ ปล่อย sequences เหล่านั้นไว้ไม่ decode ทำให้ได้ output ที่ผิด
ทำไม decodeURIComponent() ถึง throw URIError สำหรับ strings บางตัว?
decodeURIComponent() throw URIError: URI malformed เมื่อ input มี % ที่ไม่ตามด้วยสอง valid hexadecimal digits พอดี สาเหตุทั่วไป: % เดี่ยวๆ ที่ท้าย string ("50% off" ผู้ใช้พิมพ์), sequence ไม่สมบูรณ์ ("%A") หรือคู่ non-hex ("%GH") เกิดขึ้นบ่อยที่สุดกับ search queries ที่ผู้ใช้พิมพ์หรือค่าที่ paste จากข้อความที่ไม่เคยตั้งใจจะ URL-encode การแก้ไขคือครอบ decodeURIComponent() ใน try/catch block และ return raw string เมื่อเกิด error decode-uri-component npm package มีการแก้ไขเดียวกันโดยไม่ต้องการ try/catch wrapper
URLSearchParams decode percent-encoded values อัตโนมัติหรือไม่?
ใช่ ทุกค่าที่ return โดย URLSearchParams.get() และ URLSearchParams.getAll() ถูก decode ครบถ้วน — คุณไม่ควรเรียก decodeURIComponent() บน output ของพวกมัน URLSearchParams ยังปฏิบัติตาม application/x-www-form-urlencoded specification ที่ decode + เป็นช่องว่าง ทำให้ถูกต้องสำหรับการส่ง HTML form และ OAuth token responses กรณีเดียวที่ URLSearchParams ช่วยไม่ได้คือการ decode ค่าที่ encode แบบ standalone ที่ไม่ใช่ส่วนหนึ่งของ query string — สำหรับนั้นใช้ decodeURIComponent() กับ try/catch
วิธี decode เครื่องหมาย + เป็นช่องว่างใน JavaScript ทำอย่างไร?
decodeURIComponent() ถือว่า + เป็นเครื่องหมายบวก literal — ไม่แปลง + เป็นช่องว่าง นี่เป็นความตั้งใจ: + เป็นช่องว่างเป็น convention ของ application/x-www-form-urlencoded แยกจาก percent-encoding standard เพื่อ decode + เป็นช่องว่างใช้ URLSearchParams (ซึ่งปฏิบัติตาม form-encoded spec) หรือแทนที่ + ก่อนเรียก decodeURIComponent(): decodeURIComponent(str.replace(/\+/g, ' ')) สังเกตว่าการแทนที่ + ด้วย %20 ก่อน decode ก็ใช้ได้แต่ verbose กว่าเล็กน้อย ควร prefer URLSearchParams เสมอสำหรับการ parse full query strings — มันจัดการทั้ง %20 และ + ถูกต้อง
วิธี decode URL string ใน Node.js ทำอย่างไร?
ใช้ global functions เดียวกันที่มีในทุก browsers — ไม่ต้อง import ใน Node.js 10+ decodeURIComponent() สำหรับค่าแต่ละตัว decodeURI() สำหรับ URL strings ทั้งหมด URLSearchParams สำหรับ query strings สำหรับ URL ของ HTTP request ที่เข้ามาใช้ new URL(req.url, 'http://localhost') — URL constructor normalise URL และ expose .searchParams (auto-decoded) และ .pathname (decoded ที่ URL level) ฟังก์ชัน querystring.unescape() เก่ายังคงมีอยู่แต่ deprecated ตั้งแต่ Node.js 16 — prefer decodeURIComponent()
วิธี parse URL query string ให้เป็น object ใน JavaScript ทำอย่างไร?
ใช้ URLSearchParams กับ Object.fromEntries(): const params = Object.fromEntries(new URLSearchParams(queryString)) นี่ให้ plain object พร้อม parameter keys ทั้งหมดและค่าที่ decode แล้ว สังเกตว่า Object.fromEntries() เก็บเฉพาะค่าสุดท้ายสำหรับ duplicate keys — สำหรับ repeated keys เช่น tag=one&tag=two ใช้ URLSearchParams.getAll('tag') แทน สำหรับ browser new URLSearchParams(window.location.search) parse query string ของหน้าปัจจุบันอัตโนมัติ สำหรับ Node.js parse จาก new URL(req.url, base).searchParams

เครื่องมือที่เกี่ยวข้อง

สำหรับการ decode ด้วยคลิกเดียวโดยไม่ต้องเขียน code ให้วาง percent-encoded string ของคุณ ลงใน URL Decoder ของ ToolDeck โดยตรง — มัน decode ทันทีในเบราว์เซอร์ พร้อมผลลัพธ์สำหรับคัดลอกไปใน code หรือ terminal ของคุณ

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.

MW
Marcus Webbผู้ตรวจสอบทางเทคนิค

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.