URL Decode JavaScript — decodeURIComponent()

·Front-end & Node.js Developer·ZkontrolovánoMarcus Webb·Publikováno

Používejte bezplatný URL Decode Online přímo v prohlížeči — bez instalace.

Vyzkoušet URL Decode Online online →

Procentem zakódované řetězce se v JavaScriptu vyskytují neustále — vyhledávací dotaz přichází jako q=standing+desk%26price%3A200, OAuth přesměrování jako next=https%3A%2F%2Fdashboard.internal%2F, cesta k souboru jako reports%2F2025%2Fq1.pdf. Dekódování URL v JavaScriptu spočívá v výběru správné ze tří vestavěných funkcí: decodeURIComponent(), decodeURI() a URLSearchParams — a volba mezi nimi je hlavní příčinou tiché korupce dat, kterou jsem viděl v produkčních kódech, zejména okrajový případ + jako mezera a dvojité dekódování. Pro rychlé jednorázové dekódování bez psaní kódu zvládne URL Decoder od ToolDeck to okamžitě v prohlížeči. Tento výukový kurz dekódování URL v JavaScriptu pokrývá všechny tři funkce do hloubky (ES2015+ / Node.js 10+): kdy použít kterou, jak se liší pro mezery a rezervované znaky, dekódování ze souborů a HTTP požadavků, bezpečné zpracování chyb a čtyři chyby způsobující nejvíce záludných produkčních problémů.

  • decodeURIComponent() dekóduje všechny procentem zakódované sekvence — je to správná volba pro jednotlivé hodnoty query parametrů a segmenty cesty
  • decodeURI() zachovává strukturální znaky URI jako / ? & = # : — používejte ho pouze při dekódování celého URL řetězce, nikdy pro jednotlivé hodnoty
  • URLSearchParams.get() vrací již dekódované hodnoty — volání decodeURIComponent() na jeho výstup způsobuje dvojité dekódování
  • decodeURIComponent() NEDEKÓDUJE + jako mezeru — pro form-zakódovaná data (application/x-www-form-urlencoded) použijte URLSearchParams nebo nahraďte + před dekódováním
  • Vždy obalte decodeURIComponent() blokem try/catch, když vstup pochází z uživatelských dat — holé % nebo neúplná sekvence vyhodí URIError

Co je dekódování URL?

Procentové kódování (formálně definované v RFC 3986) nahrazuje znaky, které jsou v URL nebezpečné nebo strukturálně významné, znakem % následovaným dvěma hexadecimálními číslicemi — hodnotou UTF-8 bajtu daného znaku. Dekódování URL tuto transformaci obrátí: každá %XX sekvence se převede zpět na původní bajt a výsledná bytová sekvence se interpretuje jako UTF-8 text. Mezera dekódovaná z %20, lomítko z %2F, non-ASCII znak ü z %C3%BC (jeho dvou-bajtová UTF-8 reprezentace).

Znaky, které se nikdy nekódují — takzvané nerezervované znaky — jsou písmena A–Z a a–z, číslice 0–9 a - _ . ~. Vše ostatní má buď strukturální roli v URL (jako / pro oddělování segmentů cesty nebo & pro oddělování query parametrů), nebo musí být zakódováno při použití jako datová hodnota. Praktický výsledek: vyhledávací filtr jako status=active&tier=premium zakódovaný jako jediná hodnota query parametru vypadá při příchodu naprosto jinak než originál.

Before · text
After · text
// Procentem zakódováno — jak přijato v HTTP požadavku nebo webhook payloadu
"q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// Po dekódování URL — původní Elasticsearch dotazovací filtr
"q=price:[200 TO 800] AND brand:Northwood & status:in-stock"

decodeURIComponent() — Standardní funkce pro dekódování hodnot

decodeURIComponent() je tahounem dekódování URL v JavaScriptu. Dekóduje každou %XX sekvenci ve vstupním řetězci — včetně znaků se strukturálním významem v URL, jako %2F (lomítko), %3F (otazník), %26 (ampersand) a %3D (rovnítko). To z ní dělá správnou volbu pro dekódování jednotlivých hodnot query parametrů a segmentů cesty, ale špatnou volbu pro dekódování celého URL — kde tyto strukturální znaky musí zůstat zakódované. Jde o globální funkci: žádný import není potřeba v žádném JavaScriptovém prostředí.

Minimální funkční příklad

JavaScript (browser / Node.js)
// Dekódování jednotlivých hodnot query parametrů — běžný případ

const city     = decodeURIComponent('S%C3%A3o%20Paulo')          // 'São Paulo'
const district = decodeURIComponent('It%C3%A1im%20Bibi')         // 'Itáim Bibi'
const category = decodeURIComponent('office%20furniture')         // 'office furniture'
const filter   = decodeURIComponent('price%3A%5B200+TO+800%5D')  // 'price:[200+TO+800]'
// Poznámka: + se NEDEKÓDUJE jako mezera — viz sekci + v porovnávací tabulce

console.log(city)      // São Paulo
console.log(district)  // Itáim Bibi
console.log(category)  // office furniture
console.log(filter)    // price:[200+TO+800]

Dekódování přesměrovávací URL extrahované z query parametru

JavaScript
// Přesměrovávací URL byla procentem zakódována, když byla vložena jako hodnota parametru
// přijímající strana: extrahujte ji, pak ji použijte — s URLSearchParams není potřeba ruční dekódování

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')       // Automaticky dekódováno pomocí URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'

// rawNext je již dekódováno: 'https://dashboard.internal/reports?view=weekly&team=platform'
// NEVOLEJTE decodeURIComponent(rawNext) znovu — bylo by to dvojité dekódování

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

Dekódování non-ASCII a Unicode segmentů cesty

JavaScript
// REST API s internacionalizovanými segmenty cesty
// Každý UTF-8 bajt původního znaku byl zakódován samostatně

const encodedSegments = [
  '%E6%9D%B1%E4%BA%AC',   // 東京  (Tokio)   — 3 bajty na znak
  'M%C3%BCnchen',         // München          — ü zakódováno jako 2 bajty
  'caf%C3%A9',            // café             — é jako předskládané NFC
  'S%C3%A3o%20Paulo',     // São Paulo
]

encodedSegments.forEach(seg => {
  console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo

// Extrakce klíče souboru z URL storage API
// Klíč objektu obsahoval /, proto byl zakódován jako %2F uvnitř segmentu cesty
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey     = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname dekóduje kódování na úrovni URL, ale %2F (jako %252F na úrovni URL) zůstane
// Použijte decodeURIComponent pro poslední krok:
const fileKey = decodeURIComponent(rawKey)  // 'reports/2025/q1-financials.pdf'
console.log(fileKey)
Poznámka:decodeURIComponent() vyhodí URIError, když vstup obsahuje %, které není následováno dvěma platnými hexadecimálními číslicemi — například holé % na konci řetězce nebo sekvence jako %GH. Vždy ji obalte blokem try/catch při dekódování vstupu zadaného uživatelem. Bezpečné vzory dekódování jsou popsány v sekci Ošetření chyb níže.

Funkce pro dekódování URL v JavaScriptu — Referenční tabulka znaků

Tři vestavěné funkce pro dekódování se liší v přesně tom, které zakódované sekvence dekódují. Tabulka ukazuje chování pro znaky, na kterých v praxi záleží nejvíce:

ZakódovánoZnakdecodeURIComponent()decodeURI()URLSearchParams
%20mezeramezera ✅mezera ✅mezera ✅
+plus (form)+ (zachováno)+ (zachováno)mezera ✅
%2B+ literál+ ✅+ ✅+ ✅
%26&& ✅& (zach.) ❌& ✅
%3D== ✅= (zach.) ❌= ✅
%3F?? ✅? (zach.) ❌? ✅
%23## ✅# (zach.) ❌# ✅
%2F// ✅/ (zach.) ❌/ ✅
%3A:: ✅: (zach.) ❌: ✅
%40@@ ✅@ (zach.) ❌@ ✅
%25%% ✅% ✅% ✅
%C3%BCüü ✅ü ✅ü ✅

Dva kritické řádky jsou + a strukturální znaky (%26, %3D, %3F). URLSearchParams dekóduje + jako mezeru, protože dodržuje specifikaci application/x-www-form-urlencoded — správné pro HTML formuláře, ale odlišné od chování decodeURIComponent() se stejným znakem. A decodeURI() tiše přeskakuje %26, %3D a %3F — což vypadá správně, dokud hodnota skutečně neobsahuje zakódovaný ampersand nebo rovnítko.

decodeURI() — Dekódování celého URL bez zničení struktury

decodeURI() je protějškem encodeURI(). Dekóduje celý URL řetězec a přitom zachovává znaky se strukturálním významem v URI: ; , / ? : @ & = + $ #. Ty zůstávají v procentem zakódované formě (nebo jako doslovné znaky, pokud se v nezakódované podobě vyskytly ve vstupu). To dělá decodeURI() bezpečným pro sanitizaci celých URL, které mohou obsahovat non-ASCII znaky v cestě nebo názvu hostitele, aniž by omylem zničily strukturu query stringu.

Sanitizace URL s non-ASCII segmenty cesty

JavaScript
// CDN URL s internacionalizovanými segmenty cesty a strukturovaným query stringem
// decodeURI() dekóduje non-ASCII cestu, ale zachovává ? & = nedotčené

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 dekódováno; ? & = zachovány — URL zůstává strukturálně platné

// decodeURIComponent by URL zničilo — : / ? & = by byly dekódovány najednou
const broken = decodeURIComponent(encodedUrl)
// Vypadá zde stejně, ale URL jako 'https%3A%2F%2F...' by bylo zničeno
Poznámka:Upřednostněte konstruktor URL před decodeURI(), když potřebujete přistupovat také k jednotlivým komponentám URL. new URL(str) normalizuje vstup, ověřuje jeho strukturu a zpřístupňuje .pathname, .searchParams a .hostname jako již dekódované vlastnosti. Rezervujte decodeURI() pro případy, kdy potřebujete pouze řetězcový výsledek a nemůžete použít konstruktor URL (například ve velmi starých prostředích Node.js 6 bez globální třídy URL).

URLSearchParams — Automatické dekódování pro query stringy

URLSearchParams je idiomatický způsob parsování query stringů v moderním JavaScriptu. Každá hodnota vrácená metodami .get(), .getAll() nebo iterací je automaticky dekódována — včetně + jako mezery pro form-zakódovaná data. Je globálně dostupná ve všech moderních prohlížečích a Node.js 10+, nevyžaduje import a ošetřuje okrajové případy, které ruční rozdělování řetězců kazí.

Parsování příchozího query stringu

JavaScript (browser / Node.js 10+)
// Parsování webhook callbacku nebo OAuth přesměrovávacího query stringu
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'       ← + dekódováno jako mezera
console.log(params.get('filter'))        // 'price:[200+TO+800]'      ← %3A a %5B dekódovány
console.log(params.getAll('tag'))        // ['ergonomic', 'adjustable']
console.log(params.get('redirect'))      // 'https://dashboard.internal/orders?view=pending'

// Iterace přes všechny parametry
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

Parsování query parametrů z aktuálního URL prohlížeče

JavaScript (browser)
// Aktuální 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   (+ automaticky dekódováno)
console.log(filters.category)  // office furniture

Dekódování URL-zakódovaných dat ze souborů a API odpovědí

Ve skutečných projektech se neustále vyskytují dva scénáře: zpracování souboru na disku obsahujícího procentem zakódovaná data (přístupové logy, exporty dat, soubory zachycených webhooků) a parsování URL příchozího HTTP požadavku v Node.js serveru. Oba se řídí stejným principem — používejte konstruktor URL nebo URLSearchParams namísto ručního rozdělování řetězců — ale detaily se liší.

Čtení a dekódování URL-zakódovaných záznamů ze souboru

JavaScript (Node.js 10+)
import { createReadStream } from 'fs'
import { createInterface } from 'readline'

// Soubor: orders-export.txt — jeden URL-zakódovaný záznam na řádek
// customer_name=Tom%C3%A1%C5%A1+Nov%C3%A1k&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=149.99%20EUR
// customer_name=Jana+Proch%C3%A1zkov%C3%A1&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=89.00%20EUR
// customer_name=Tom%C3%A1%C5%A1+Nov%C3%A1k&order_id=ord_2e8d5f&product=Monitor+Arm&total=59.00%20EUR

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 dekóduje + jako mezeru a %XX sekvence automaticky
    const params = new URLSearchParams(line)

    orders.push({
      customerName: params.get('customer_name'),  // 'Tomáš Novák'
      orderId:      params.get('order_id'),        // 'ord_9c2f4a'
      product:      params.get('product'),         // 'Standing Desk Pro'
      total:        params.get('total'),           // '149.99 EUR'
    })
  }

  return orders
}

const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'Tomáš Novák', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '149.99 EUR' }

Parsování Nginx přístupového logu pro dekódování vyhledávacích dotazů

JavaScript (Node.js)
import { createReadStream } from 'fs'
import { createInterface } from 'readline'

// Řádky Nginx přístupového logu vypadají takto:
// 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) {
    // Extrakce cesty požadavku z řádku logu
    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 dekóduje automaticky
    } catch {
      // Přeskočit poškozené řádky — přístupové logy mohou obsahovat zkrácené záznamy
    }
  }

  return queries
}

const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']

Parsování query parametrů v Node.js HTTP serveru

JavaScript (Node.js 10+)
import http from 'http'

// Příchozí URL: /api/products?q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D

const server = http.createServer((req, res) => {
  // Sestavení celého URL — druhý argument je základ vyžadovaný konstruktorem URL
  const requestUrl = new URL(req.url!, 'http://localhost')

  const searchQuery = requestUrl.searchParams.get('q')            // 'standing desk'
  const warehouseId = requestUrl.searchParams.get('warehouse')    // 'eu-west-1'
  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: 'warehouse parameter is required' }))
    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)

Když potřebujete prozkoumat zakódované URL během vývoje — abyste pochopili, co webhook posílá, ještě než napíšete kód pro parsování — vložte ho přímo do URL Decoder od ToolDeck a okamžitě uvidíte dekódovanou podobu bez spuštění skriptu.

Dekódování URL z příkazové řádky

Pro shell skripty, CI pipeline nebo rychlou jednorázovou kontrolu zakódovaných řetězců funguje několik přístupů bez psaní celého skriptu. Node.js jednořádkové příkazy jsou multiplatformní; na macOS a Linuxu je vždy dostupný také python3.

bash
# ── Jednořádkové příkazy Node.js ─────────────────────────────────────────

# Dekódování jedné procentem zakódované hodnoty
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio

# Parsování query stringu a výpis každého páru klíč=hodnota (dekódovaného)
node -e "
  const params = new URLSearchParams(process.argv[1])
  for (const [k, v] of params) console.log(`${k} = ${v}`)
" "q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10"
# q = standing desk
# warehouse = eu-west-1
# minStock = 10

# Dekódování a pěkný výpis URL-zakódovaného JSON těla (běžné při ladění webhooků)
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%22EUR%22%7D'
# {
#   "event": "purchase",
#   "amount": 149.99,
#   "currency": "EUR"
# }

# ── Jednořádkový příkaz Python (dostupný na většině macOS/Linux systémů) ──
# unquote_plus také dekóduje + jako mezeru — správné pro form-zakódovaná 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 — zachycení a dekódování přesměrovávacího URL z hlavičky odpovědi ──────────
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))
  "

Tolerantní dekódování s decode-uri-component

Vestavěná funkce decodeURIComponent() vyhodí URIError při jakékoli poškozené sekvenci — včetně koncového % bez dvou následujících hexadecimálních číslic. V produkčním kódu zpracovávajícím URL zadané uživatelem nebo třetí stranou — logy vyhledávacích dotazů, proklikávací URL z e-mailových kampaní, scrapovaná webová data — jsou poškozené sekvence natolik časté, že způsobují pády. Balíček decode-uri-component (~30 mil. týdenních stažení z npm) zpracovává poškozené sekvence tolerantně tím, že vrací původní sekvenci beze změny místo vyhazování výjimky.

bash
npm install decode-uri-component
# nebo
pnpm add decode-uri-component
JavaScript (Node.js)
import decodeUriComponent from 'decode-uri-component'

// Nativní funkce vyhazuje výjimku při poškozeném vstupu — může zhavarovat obslužnou rutinu požadavku
try {
  decodeURIComponent('product%name%')   // ❌ URIError: URI malformed
} catch (e) {
  console.error('Nativní vyhodilo:', (e as Error).message)
}

// decode-uri-component vrací surovou sekvenci místo vyhazování výjimky
console.log(decodeUriComponent('product%name%'))
// product%name%   ← poškozené sekvence ponechány beze změny, žádná výjimka

// Platné sekvence jsou správně dekódovány
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio

// Smíšené: platné sekvence dekódovány, neplatné zachovány
console.log(decodeUriComponent('Praha%20Office%20%ZZ%20HQ'))
// Praha Office %ZZ HQ   ← %ZZ není platný hex — ponecháno beze změny

// Praktické použití v pipeline zpracování logů
const rawSearchQueries = [
  'standing%20desk%20ergonomic',
  'price%3A%5B200+TO+800%5D',
  '50%25+off',       // ← by vyhazovalo s decodeURIComponent (holé %)
  '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']

Používejte decodeURIComponent() s blokem try/catch pro aplikační kód, kde chcete okamžitě vědět o příchodu neočekávaného vstupu. Sáhněte po decode-uri-component v datových pipeline, procesorech logů a obslužných rutinách webhooků, kde chcete pokračovat ve zpracování i když některé vstupy obsahují neplatné kódování.

Zpracování poškozených procentem zakódovaných řetězců

Holé %, neúplná sekvence jako %A nebo neplatný pár jako %GH způsobují, že decodeURIComponent() vyhodí URIError: URI malformed. Jakýkoli vstup kontrolovaný uživatelem — vyhledávací dotazy, fragmenty URL, formulářová pole obsahující URL, parametry z odkazů e-mailových kampaní — může tyto sekvence obsahovat. Bezpečná obálka je nezbytná pro jakýkoli externě orientovaný kód.

Bezpečné obálky dekódování pro běžné scénáře

JavaScript
// ── 1. Základní bezpečné dekódování — při chybě vrátí původní řetězec ────────────────
function safeDecode(encoded: string): string {
  try {
    return decodeURIComponent(encoded)
  } catch {
    return encoded  // vrátit surový vstup pokud dekódování selže — nikdy nespadnout
  }
}

// ── 2. Bezpečné dekódování + zpracování + jako mezery (pro form-zakódované hodnoty) ──
function safeFormDecode(formEncoded: string): string {
  try {
    return decodeURIComponent(formEncoded.replace(/+/g, ' '))
  } catch {
    return formEncoded.replace(/+/g, ' ')  // alespoň nahradit + i když zbytek selže
  }
}

// ── 3. Bezpečný úplný parser query stringu → prostý objekt ───────────────────────────
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 zpracovává veškeré dekódování interně
    }
  } catch {
    // Tiše vrátit prázdný objekt při zcela poškozeném vstupu
  }
  return result
}

// Použití
console.log(safeDecode('S%C3%A3o%20Paulo'))         // São Paulo
console.log(safeDecode('search%20for%2050%25+off'))  // search for 50% off (holé %)
                                                     // → ve skutečnosti v pořádku; % zde je %25
console.log(safeDecode('malformed%string%'))         // malformed%string%  (žádná výjimka)

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' }

Časté chyby

Viděl jsem, jak tyto čtyři vzory způsobují tiché poškození dat nebo neočekávané pády v produkci — obvykle pouze tehdy, když hodnota náhodou obsahuje speciální znak, což znamená, že projdou unit testy a vyjeví se až s reálnými uživatelskými daty.

Chyba 1 — Dvojité dekódování hodnoty URLSearchParams

Problém: URLSearchParams.get() vrací již dekódovaný řetězec. Volání decodeURIComponent() na něj dekóduje hodnotu dvakrát — přeměňuje zbývající % znaky v dekódovaném výstupu na %25, čímž data poškodí. Oprava: Použijte hodnotu z URLSearchParams.get() přímo — žádné další dekódování není potřeba.

Before · JavaScript
After · JavaScript
// ❌ URLSearchParams již dekódovalo — opětovné dekódování poškodí hodnoty obsahující %
const qs        = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate   = qs.get('rate')       // '50%'   ← již dekódováno
const wrongRate = decodeURIComponent(rawRate)
// '50%25'  ← % bylo při druhém průchodu dekódováno na %25 — nyní opět špatně
// ✅ Použijte URLSearchParams.get() přímo — dekódování je automatické
const qs       = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate     = qs.get('rate')        // '50%'   ← správně, žádný další krok není potřeba
const redirect = qs.get('redirect')    // 'https://dashboard.internal/'
const parsed   = new URL(redirect!)    // Bezpečné ke konstrukci — již dekódováno
console.log(parsed.hostname)           // dashboard.internal

Chyba 2 — Nedekódování + jako mezery pro form-zakódovaná data

Problém: Formulářová odeslání a některé OAuth knihovny kódují mezery jako + (application/x-www-form-urlencoded). Volání decodeURIComponent() na tato data ponechá + jako doslovný znak plus. Oprava: Použijte URLSearchParams pro parsování form-zakódovaných dat, nebo nahraďte + mezerou před voláním decodeURIComponent().

Before · JavaScript
After · JavaScript
// ❌ decodeURIComponent zachází s + jako doslovným znakem plus, ne mezerou
// OAuth token endpoint posílá: grant_type=authorization_code&code=SplxlOBeZQQYb...
//   &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// Některé starší OAuth implementace také používají + pro mezery v kódech
const formBody   = 'customer_name=Tom%C3%A1%C5%A1+Nov%C3%A1k&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name       = decodeURIComponent(rawVal)
console.log(name)  // 'Tomáš+Novák'  ← + nepřevedeno na mezeru
// ✅ Použijte URLSearchParams — dodržuje specifikaci application/x-www-form-urlencoded
const formBody   = 'customer_name=Tom%C3%A1%C5%A1+Nov%C3%A1k&product=Standing+Desk+Pro&qty=2'
const params     = new URLSearchParams(formBody)
console.log(params.get('customer_name'))  // 'Tomáš Novák'   ← + správně dekódováno jako mezera
console.log(params.get('product'))        // 'Standing Desk Pro'

Chyba 3 — Použití decodeURI() pro jednotlivé hodnoty query parametrů

Problém: decodeURI() nedekóduje %26 (&), %3D (=) ani %3F (?) — znaky běžně zakódované v hodnotách parametrů. Použití pro dekódování jedné hodnoty ponechá tyto sekvence nedotčené a tiše produkuje nesprávný výstup. Oprava: Používejte decodeURIComponent() pro jednotlivé hodnoty; rezervujte decodeURI() pro celé URL řetězce.

Before · JavaScript
After · JavaScript
// ❌ decodeURI nedekóduje & a = — hodnota zůstane poškozená
const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west'
const filter        = decodeURI(encodedFilter)
console.log(filter)
// 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west'  ← %3D a %26 nedekódovány
// ✅ decodeURIComponent dekóduje všechny sekvence včetně & a =
const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west'
const filter        = decodeURIComponent(encodedFilter)
console.log(filter)
// 'status=active&tier=premium&region=eu-west'  ← správně dekódováno

Chyba 4 — Neobalení decodeURIComponent blokem try/catch pro uživatelský vstup

Problém: Holé %bez dvou hexadecimálních číslic — běžné ve vyhledávacích dotazech zadaných uživatelem (“50% sleva”, “100% bavlna”) — způsobí, že decodeURIComponent() vyhodí URIError: URI malformed a způsobí pád obslužné rutiny požadavku, pokud není zachyceno. Oprava: Vždy obalte blokem try/catch, když vstup pochází z uživatelských dat, fragmentů URL nebo externích systémů.

Before · JavaScript
After · JavaScript
// ❌ Uživatel zadal '100% bavlna' do vyhledávacího pole — holé % způsobí pád serveru
// GET /api/search?q=100%25+bavlna  ← Tento konkrétní případ je v pořádku (%25 = %)
// GET /api/search?q=100%+bavlna    ← Toto způsobí pád (% není následováno 2 hexadecimálními číslicemi)
app.get('/api/search', (req, res) => {
  const query = decodeURIComponent(req.query.q as string)
  // ↑ vyhodí URIError: URI malformed pro '100% bavlna'  → neošetřená chyba 500
})
// ✅ Obalte blokem try/catch — při selhání dekódování použijte surový vstup
app.get('/api/search', (req, res) => {
  let query: string
  try {
    query = decodeURIComponent(req.query.q as string)
  } catch {
    query = req.query.q as string  // použít surovou hodnotu místo pádu
  }
  // pokračovat bezpečně ve zpracování — query je buď dekódováno nebo surové
})

decodeURIComponent vs decodeURI vs URLSearchParams — Rychlé srovnání

MetodaDekóduje %XXDekóduje + jako mezeruVyhazuje při poškozeném vstupuDekóduje & = ? #Případ použitíVyžaduje instalaci
decodeURIComponent()✅ vše❌ ne✅ URIError✅ anoJednotlivé hodnoty a segmenty cestyNe
decodeURI()✅ většina❌ ne✅ URIError❌ neCelé URL řetězceNe
URLSearchParams✅ vše✅ ano❌ tiché✅ anoParsování query stringu s automatickým dekódovánímNe
URL konstruktor✅ vše✅ ano✅ TypeError✅ anoÚplné parsování a normalizace URLNe
decode-uri-component✅ vše❌ ne❌ tiché✅ anoHromadné dekódování s tolerancí poškozeného vstupunpm install
querystring.unescape()✅ vše❌ ne❌ tiché✅ anoStarší Node.js (zastaralé od v16)Ne (vestavěno)

V naprosté většině případů se volba redukuje na tři scénáře. Používejte URLSearchParams pro parsování query stringů — automaticky zpracovává dekódování, pravidlo + jako mezery a opakované klíče. Používejte decodeURIComponent() (obalené blokem try/catch) pro jednu hodnotu nebo segment cesty, zejména když očekáváte lomítka zakódovaná jako %2F uvnitř segmentu. Používejte decodeURI() pouze tehdy, když máte celý URL řetězec s non-ASCII znaky v cestě a potřebujete, aby strukturální znaky (/ ? & =) zůstaly zakódované ve výstupu.

Často kladené otázky

Jaký je rozdíl mezi decodeURIComponent() a decodeURI() v JavaScriptu?
decodeURIComponent() dekóduje každou procentem zakódovanou sekvenci v řetězci, včetně znaků se strukturálním významem v URL — & (%26), = (%3D), ? (%3F), # (%23), / (%2F) a : (%3A). Je navržena pro dekódování jednotlivých hodnot query parametrů nebo segmentů cesty. decodeURI() tyto strukturální znaky zachovává — nedekóduje %26, %3D, %3F, %23, %2F ani %3A — protože je navržena pro sanitizaci celého URL bez poškození struktury query stringu. Použití decodeURI() na jednu hodnotu parametru obsahující zakódovaný & nebo = tiše ponechá tyto sekvence nedekódované a vyprodukuje nesprávný výstup.
Proč decodeURIComponent() vyhazuje URIError pro některé řetězce?
decodeURIComponent() vyhazuje URIError: URI malformed, pokud vstup obsahuje % nenásledované přesně dvěma platnými hexadecimálními číslicemi. Časté spouštěče: holé % na konci řetězce ("50% sleva" zadané uživatelem), neúplná sekvence ("%A") nebo neplatný hexadecimální pár ("%GH"). Nejčastěji se to stává u vyhledávacích dotazů zadaných uživatelem nebo u hodnot vložených z textu, který nikdy nebyl zamýšlen jako URL-zakódovaný. Opravou je obalit decodeURIComponent() blokem try/catch a při chybě vrátit surový řetězec. Balíček decode-uri-component na npm nabízí stejnou opravu bez potřeby obálky try/catch.
Dekóduje URLSearchParams procentem zakódované hodnoty automaticky?
Ano. Každá hodnota vrácená metodami URLSearchParams.get() a URLSearchParams.getAll() je plně dekódována — nikdy byste neměli volat decodeURIComponent() na jejich výstup. URLSearchParams také dodržuje specifikaci application/x-www-form-urlencoded, která dekóduje + jako mezeru, což je správné pro HTML formuláře a odpovědi OAuth tokenů. Jediný případ, kdy URLSearchParams nemůže pomoci, je dekódování samostatné zakódované hodnoty, která není součástí query stringu — pro to použijte decodeURIComponent() s blokem try/catch.
Jak dekóduji znak + jako mezeru v JavaScriptu?
decodeURIComponent() zachází s + jako doslovným znakem plus — nepřevádí + na mezeru. To je záměrné: + jako mezera je konvence application/x-www-form-urlencoded, oddělená od standardu procentového kódování. Pro dekódování + jako mezery použijte URLSearchParams (které dodržuje specifikaci form-zakódování), nebo nahraďte + před voláním decodeURIComponent(): decodeURIComponent(str.replace(/\+/g, ' ')). Všimněte si, že nahrazení + za %20 před dekódováním také funguje, ale je mírně podrobnější. Vždy upřednostňujte URLSearchParams pro parsování celých query stringů — správně zpracovává jak %20, tak +.
Jak dekóduji řetězec přes URL v Node.js?
Použijte stejné globální funkce dostupné ve všech prohlížečích — v Node.js 10+ není potřeba import. decodeURIComponent() pro jednotlivé hodnoty, decodeURI() pro celé URL řetězce, URLSearchParams pro query stringy. Pro URL příchozích HTTP požadavků použijte new URL(req.url, 'http://localhost') — konstruktor URL normalizuje URL a zpřístupňuje .searchParams (automaticky dekódované) a .pathname (dekódované na úrovni URL). Starší funkce querystring.unescape() stále existuje, ale je zastaralá od Node.js 16 — místo ní použijte decodeURIComponent().
Jak zparsuju URL query string do objektu v JavaScriptu?
Použijte URLSearchParams s Object.fromEntries(): const params = Object.fromEntries(new URLSearchParams(queryString)). To dá prostý objekt se všemi klíči parametrů a dekódovanými hodnotami. Všimněte si, že Object.fromEntries() při duplicitních klíčích ponechá pouze poslední hodnotu — pro opakované klíče jako tag=one&tag=two použijte místo toho URLSearchParams.getAll('tag'). Pro prohlížeč new URLSearchParams(window.location.search) automaticky zparsuje query string aktuální stránky. Pro Node.js parsujte z new URL(req.url, base).searchParams.

Související nástroje

Pro jednoklikové dekódování bez psaní kódu vložte svůj procentem zakódovaný řetězec přímo do URL Decoder od ToolDeck — okamžitě dekóduje v prohlížeči a výsledek je připraven ke zkopírování do kódu nebo terminálu.

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 WebbTechnický recenzent

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.