URL Decode JavaScript — decodeURIComponent()
Użyj darmowego Dekoder URL Online bezpośrednio w przeglądarce — bez instalacji.
Wypróbuj Dekoder URL Online online →Ciągi zakodowane procentowo pojawiają się w kodzie JavaScript nieustannie — zapytanie wyszukiwania przybywa jako q=standing+desk%26price%3A200, przekierowanie OAuth jako next=https%3A%2F%2Fdashboard.internal%2F, ścieżka do pliku jako reports%2F2025%2Fq1.pdf. Dekodowanie URL w JavaScript sprowadza się do wyboru właściwej spośród trzech wbudowanych funkcji: decodeURIComponent(), decodeURI() i URLSearchParams — wybór między nimi jest główną przyczyną większości cichych uszkodzeń danych, jakie widziałem w produkcyjnych bazach kodu, szczególnie przypadek krawędziowy ze znakiem + jako spacja i podwójne dekodowanie. Aby szybko jednorazowo zdekodować bez pisania kodu, Dekoder URL ToolDeck obsługuje to natychmiast w przeglądarce. Ten samouczek JavaScript dekodowania URL omawia wszystkie trzy funkcje dogłębnie (ES2015+ / Node.js 10+): kiedy używać każdej z nich, jak różnią się obsługą spacji i znaków zastrzeżonych, dekodowanie z plików i żądań HTTP, bezpieczna obsługa błędów i cztery błędy powodujące najbardziej subtelne błędy produkcyjne.
- ✓decodeURIComponent() dekoduje wszystkie sekwencje zakodowane procentowo — to właściwy wybór dla wartości poszczególnych parametrów zapytania i segmentów ścieżki
- ✓decodeURI() zachowuje strukturalne znaki URI takie jak / ? & = # : — używaj go tylko do dekodowania pełnego ciągu URL, nigdy dla pojedynczych wartości
- ✓URLSearchParams.get() zwraca już zdekodowane wartości — wywołanie decodeURIComponent() na jego wyniku powoduje podwójne dekodowanie
- ✓decodeURIComponent() NIE dekoduje + jako spacji — dla danych zakodowanych w formularzach (application/x-www-form-urlencoded) użyj URLSearchParams lub zastąp + przed dekodowaniem
- ✓Zawsze owijaj decodeURIComponent() w try/catch gdy dane wejściowe pochodzą od użytkownika — sam znak % lub niepełna sekwencja zgłasza URIError
Czym jest dekodowanie URL?
Kodowanie procentowe (formalnie zdefiniowane w RFC 3986) zastępuje znaki, które są niebezpieczne lub mają strukturalne znaczenie w URL, znakiem % i dwoma cyframi szesnastkowymi — wartością bajtu UTF-8 danego znaku. Dekodowanie URL odwraca tę transformację: każda sekwencja %XX jest konwertowana z powrotem do oryginalnego bajtu, a wynikowa sekwencja bajtów jest interpretowana jako tekst UTF-8. Spacja jest dekodowana z %20, ukośnik z %2F, znak spoza ASCII ü z %C3%BC (jego dwubajtowa reprezentacja UTF-8).
Znaki, które nigdy nie są kodowane — zwane znakami niezastrzeżonymi — to litery A–Z i a–z, cyfry 0–9 oraz - _ . ~. Wszystko inne albo ma strukturalną rolę w URL (jak / oddzielający segmenty ścieżki lub & oddzielający parametry zapytania) albo musi być zakodowane gdy używane jako dane. Praktyczny rezultat: filtr wyszukiwania jak status=active&tier=premium zakodowany jako wartość pojedynczego parametru zapytania wygląda zupełnie inaczej niż oryginał.
// Zakodowany procentowo — tak jak otrzymano w żądaniu HTTP lub ładunku webhook "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// Po dekodowaniu URL — oryginalny filtr zapytania Elasticsearch "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
decodeURIComponent() — Standardowa Funkcja do Dekodowania Wartości
decodeURIComponent() to główna funkcja robocza dekodowania URL w JavaScript. Dekoduje każdą sekwencję %XX w ciągu wejściowym — w tym znaki mające strukturalne znaczenie w URL, takie jak %2F (ukośnik), %3F (znak zapytania), %26 (ampersand) i %3D (znak równości). Czyni ją to właściwym wyborem do dekodowania poszczególnych wartości parametrów zapytania i segmentów ścieżki, ale złym wyborem do dekodowania pełnego URL — gdzie te strukturalne znaki muszą pozostać zakodowane. Jest to funkcja globalna: żaden import nie jest wymagany w żadnym środowisku JavaScript.
Minimalny działający przykład
// Dekodowanie poszczególnych wartości parametrów zapytania — typowy przypadek
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]'
// Uwaga: + NIE jest dekodowany jako spacja — patrz sekcja + w tabeli porównawczej
console.log(city) // São Paulo
console.log(district) // Itáim Bibi
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]Dekodowanie URL przekierowania wyodrębnionego z parametru zapytania
// URL przekierowania był zakodowany procentowo gdy był osadzony jako wartość parametru
// strona odbierająca: wyodrębnij, a potem użyj — bez ręcznego dekodowania przy 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') // Automatycznie zdekodowany przez URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext jest już zdekodowany: 'https://dashboard.internal/reports?view=weekly&team=platform'
// NIE wywołuj decodeURIComponent(rawNext) ponownie — to byłoby podwójne dekodowanie
const nextUrl = new URL(rawNext!)
console.log(nextUrl.hostname) // dashboard.internal
console.log(nextUrl.searchParams.get('view')) // weekly
console.log(nextUrl.searchParams.get('team')) // platformDekodowanie segmentów ścieżki spoza ASCII i Unicode
// REST API z umiędzynarodowionymi segmentami ścieżki
// Każdy bajt UTF-8 oryginalnego znaku był kodowany procentowo osobno
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (Tokio) — 3 bajty na znak
'M%C3%BCnchen', // München — ü zakodowane jako 2 bajty
'caf%C3%A9', // café — é jako złożone NFC
'S%C3%A3o%20Paulo', // São Paulo
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo
// Wyodrębnianie klucza pliku z URL API przechowywania
// Klucz obiektu zawierał / więc był zakodowany jako %2F wewnątrz segmentu ścieżki
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname dekoduje kodowanie na poziomie URL ale %2F (jako %252F na poziomie URL) pozostaje
// Użyj decodeURIComponent dla końcowego kroku:
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() zgłasza URIError gdy dane wejściowe zawierają % nie poprzedzony dwoma prawidłowymi cyframi szesnastkowymi — na przykład sam znak % na końcu ciągu lub sekwencja taka jak %GH. Zawsze owijaj w try/catch przy dekodowaniu danych wejściowych dostarczonych przez użytkownika. Wzorce bezpiecznego dekodowania są omówione w sekcji Obsługa Błędów poniżej.Funkcje Dekodowania URL w JavaScript — Tabela Znaków
Trzy wbudowane funkcje dekodowania różnią się dokładnie tym, jakie zakodowane sekwencje dekodują. Tabela pokazuje zachowanie dla znaków najważniejszych w praktyce:
| Zakodowany | Znak | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | spacja | spacja ✅ | spacja ✅ | spacja ✅ |
| + | + (formularz) | + (zachowany) | + (zachowany) | spacja ✅ |
| %2B | + dosłownie | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (zachowany) ❌ | & ✅ |
| %3D | = | = ✅ | = (zachowany) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (zachowany) ❌ | ? ✅ |
| %23 | # | # ✅ | # (zachowany) ❌ | # ✅ |
| %2F | / | / ✅ | / (zachowany) ❌ | / ✅ |
| %3A | : | : ✅ | : (zachowany) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (zachowany) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
Dwa kluczowe wiersze to + i znaki strukturalne (%26, %3D, %3F). URLSearchParams dekoduje + jako spację, ponieważ przestrzega specyfikacji application/x-www-form-urlencoded — poprawnie dla zgłoszeń formularzy HTML, ale inaczej niż decodeURIComponent() obsługuje ten sam znak. A decodeURI() po cichu pomija %26, %3D i %3F — co wygląda poprawnie dopóki wartość nie zawiera zakodowanego ampersanda lub znaku równości.
decodeURI() — Dekodowanie Pełnego URL bez Niszczenia Struktury
decodeURI() jest odpowiednikiem encodeURI(). Dekoduje pełny ciąg URLzachowując znaki mające strukturalne znaczenie w URI: ; , / ? : @ & = + $ #. Są one pozostawiane w formie zakodowanej procentowo (lub jako znaki dosłowne jeśli pojawiły się niezakodowane w danych wejściowych). Czyni to decodeURI() bezpiecznym do sanityzacji pełnych URL które mogą zawierać znaki spoza ASCII w ścieżce lub nazwie hosta, bez przypadkowego niszczenia struktury ciągu zapytania.
Sanityzacja URL z segmentami ścieżki spoza ASCII
// URL CDN z umiędzynarodowionymi segmentami ścieżki i strukturalnym ciągiem zapytania // decodeURI() dekoduje ścieżkę spoza ASCII ale zachowuje ? & = nienaruszone 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 // ↑ Znaki spoza ASCII zdekodowane; ? & = zachowane — URL pozostaje strukturalnie ważny // decodeURIComponent zniszczyłby URL — : / ? & = wszystkie zdekodowane naraz const broken = decodeURIComponent(encodedUrl) // 'https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' // Wygląda tak samo tutaj, ale URL jak 'https%3A%2F%2F...' zostałby zniszczony
URL zamiast decodeURI() gdy musisz też uzyskać dostęp do poszczególnych komponentów URL. new URL(str) normalizuje dane wejściowe, weryfikuje ich strukturę i udostępnia .pathname, .searchParams i .hostname jako już zdekodowane właściwości. Zachowaj decodeURI() dla przypadków gdy potrzebujesz tylko wyniku w postaci ciągu i nie możesz użyć konstruktora URL (na przykład w bardzo starych środowiskach Node.js 6 bez globalnej klasy URL).URLSearchParams — Automatyczne Dekodowanie Ciągów Zapytania
URLSearchParams to idiomatyczny sposób parsowania ciągów zapytania w nowoczesnym JavaScript. Każda wartość zwrócona przez .get(), .getAll() lub iterację jest automatycznie dekodowana — w tym + jako spacja dla danych zakodowanych formularzami. Jest dostępna globalnie we wszystkich nowoczesnych przeglądarkach i Node.js 10+, nie wymaga importu i obsługuje przypadki brzegowe, które ręczne dzielenie ciągów obsługuje niepoprawnie.
Parsowanie przychodzącego ciągu zapytania
// Parsowanie ciągu zapytania callbacku webhook lub przekierowania OAuth
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' ← + zdekodowany jako spacja
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A i %5B zdekodowane
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// Iteracja po wszystkich parametrach
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=pendingParsowanie parametrów zapytania z bieżącego URL przeglądarki
// Bieżący 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 (+ automatycznie zdekodowany)
console.log(filters.category) // office furnitureDekodowanie Danych Zakodowanych URL z Plików i Odpowiedzi API
Dwa scenariusze pojawiają się stale w rzeczywistych projektach: przetwarzanie pliku na dysku zawierającego dane zakodowane procentowo (logi dostępu, eksporty danych, pliki przechwytywania webhook) i parsowanie URL przychodzącego żądania HTTP w serwerze Node.js. Oba kierują się tą samą zasadą — używaj konstruktora URL lub URLSearchParams zamiast ręcznego dzielenia ciągów — ale szczegóły się różnią.
Odczytywanie i dekodowanie rekordów zakodowanych URL z pliku
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Plik: orders-export.txt — jeden rekord zakodowany URL na wiersz
// customer_name=Piotr+Kowalski&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=649%20PLN
// customer_name=Anna+Wi%C5%9Bniewska&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=449%20PLN
// customer_name=Gda%C5%84ski+Klient&order_id=ord_2e8d5f&product=Monitor+Arm&total=299%20PLN
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 dekoduje + jako spację i sekwencje %XX automatycznie
const params = new URLSearchParams(line)
orders.push({
customerName: params.get('customer_name'), // 'Piotr Kowalski'
orderId: params.get('order_id'), // 'ord_9c2f4a'
product: params.get('product'), // 'Standing Desk Pro'
total: params.get('total'), // '649 PLN'
})
}
return orders
}
const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'Piotr Kowalski', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '649 PLN' }Parsowanie logu dostępu Nginx do dekodowania zapytań wyszukiwania
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Wiersze logu dostępu Nginx wyglądają tak:
// 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) {
// Wyodrębnij ścieżkę żądania z wiersza 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 dekoduje automatycznie
} catch {
// Pomiń zniekształcone wiersze — logi dostępu mogą zawierać obcięte wpisy
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']Parsowanie parametrów zapytania w serwerze HTTP Node.js
import http from 'http'
// Przychodzący URL: /api/products?q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D
const server = http.createServer((req, res) => {
// Skonstruuj pełny URL — drugi arg wymagany przez konstruktor URL to baza
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)Gdy podczas programowania musisz sprawdzić zakodowany URL — żeby zrozumieć co wysyła webhook przed napisaniem kodu parsującego — wklej go bezpośrednio do Dekodera URL ToolDeck aby zobaczyć zdekodowaną formę natychmiast bez uruchamiania skryptu.
Dekodowanie URL w Wierszu Poleceń
Do skryptów powłoki, potoków CI lub szybkiej jednorazowej inspekcji zakodowanych ciągów kilka podejść działa bez pisania pełnego skryptu. Jednolinijkowe polecenia Node.js są wieloplatformowe; na macOS i Linux python3 jest też zawsze dostępny.
# ── Jednolinijkowe polecenia Node.js ────────────────────────────────────
# Dekodowanie pojedynczej wartości zakodowanej procentowo
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio
# Parsowanie ciągu zapytania i wydrukowanie każdej pary klucz=wartość (zdekodowanej)
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
# Dekodowanie i ładne wydrukowanie ciała JSON zakodowanego URL (typowe przy debugowaniu 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"
# }
# ── Jednolinijkowe polecenie Python (dostępne w większości systemów macOS/Linux) ──────
# unquote_plus dekoduje też + jako spację — poprawne dla danych zakodowanych formularzami
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 — zarejestruj i zdekoduj URL przekierowania z nagłówka odpowiedzi ──────
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))
"Eleganckie Dekodowanie z decode-uri-component
Wbudowany decodeURIComponent() zgłasza URIError dla każdej zniekształconej sekwencji — w tym końcowy % bez dwóch następujących cyfr szesnastkowych. W kodzie produkcyjnym przetwarzającym URL dostarczone przez użytkownika lub strony trzecie — logi zapytań wyszukiwania, URL kliknięć z kampanii emailowych, zeskrobane dane webowe — zniekształcone sekwencje są wystarczająco powszechne, żeby powodować awarie. Pakiet decode-uri-component (~30M tygodniowych pobrań npm) obsługuje zniekształcone sekwencje elegancko, zwracając oryginalną sekwencję bez zmian zamiast zgłaszać wyjątek.
npm install decode-uri-component # or pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// Natywna funkcja zgłasza przy zniekształconym wejściu — może zawiesić obsługę żądania
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('Natywna zgłosiła:', (e as Error).message)
}
// decode-uri-component zwraca surową sekwencję zamiast zgłaszać
console.log(decodeUriComponent('product%name%'))
// product%name% ← zniekształcone sekwencje pozostawione bez zmian, brak wyjątku
// Prawidłowe sekwencje są dekodowane poprawnie
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio
// Mieszane: prawidłowe sekwencje zdekodowane, nieprawidłowe zachowane
console.log(decodeUriComponent('Berlin%20Office%20%ZZ%20HQ'))
// Berlin Office %ZZ HQ ← %ZZ nie jest prawidłowym szesnastkowym — pozostawione bez zmian
// Praktyczne użycie w potoku przetwarzania logów
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← z decodeURIComponent zgłosiłoby (sam %)
'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']Używaj decodeURIComponent() z try/catch dla kodu aplikacji gdzie chcesz wiedzieć natychmiast gdy pojawi się nieoczekiwane dane wejściowe. Sięgaj po decode-uri-component w potokach danych, procesorach logów i obsługujących webhooki gdzie chcesz kontynuować przetwarzanie nawet gdy niektóre dane wejściowe zawierają nieprawidłowe kodowanie.
Obsługa Zniekształconych Ciągów Zakodowanych Procentowo
Sam znak %, niepełna sekwencja jak %A lub nieprawidłowa para jak %GH powodują, że decodeURIComponent() zgłasza URIError: URI malformed. Każde dane wejściowe kontrolowane przez użytkownika — zapytania wyszukiwania, fragmenty URL, pola formularzy zawierające URL, parametry z linków kampanii emailowych — mogą zawierać te sekwencje. Bezpieczny wrapper jest niezbędny dla każdego kodu dostępnego z zewnątrz.
Bezpieczne wrappery dekodowania dla typowych scenariuszy
// ── 1. Podstawowe bezpieczne dekodowanie — zwraca oryginalny ciąg przy błędzie ──
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // zwróć surowe wejście jeśli dekodowanie nie powiedzie się — nigdy nie zawiesza
}
}
// ── 2. Bezpieczne dekodowanie + obsługa + jako spacji (dla wartości zakodowanych formularzami) ──
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // przynajmniej zastąp + nawet jeśli reszta nie powiedzie się
}
}
// ── 3. Bezpieczny pełny parser ciągu zapytania → zwykły obiekt ──────────
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 obsługuje wszystkie dekodowania wewnętrznie
}
} catch {
// Cicho zwróć puste przy całkowicie zniekształconych danych wejściowych
}
return result
}
// Użycie
console.log(safeDecode('S%C3%A3o%20Paulo')) // São Paulo
console.log(safeDecode('search%20for%2050%25+off')) // search for 50%+off
// → właściwie w porządku; % tu oznacza %25
console.log(safeDecode('malformed%string%')) // malformed%string% (bez wyjątku)
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' }Typowe Błędy
Widziałem jak te cztery wzorce powodują ciche uszkodzenia danych lub nieoczekiwane awarie w produkcji — zazwyczaj tylko gdy wartość zawiera specjalny znak, co oznacza że prześlizgują się przez testy jednostkowe i ujawniają się na rzeczywistych danych użytkowników.
Błąd 1 — Podwójne dekodowanie wartości URLSearchParams
Problem: URLSearchParams.get() zwraca już zdekodowany ciąg. Wywołanie decodeURIComponent() na nim podwójnie dekoduje wartość — zamieniając każdy pozostały % w zdekodowanym wyjściu na %25, co niszczy dane. Poprawka: używaj wartości z URLSearchParams.get() bezpośrednio — żadne dodatkowe dekodowanie nie jest potrzebne.
// ❌ URLSearchParams już zdekodował — dekodowanie ponownie niszczy wartości z %
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← już zdekodowany
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← % był zdekodowany do %25 w drugim przejściu — teraz znowu błędny// ✅ Używaj URLSearchParams.get() bezpośrednio — dekodowanie jest automatyczne
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate = qs.get('rate') // '50%' ← poprawne, bez dodatkowego kroku
const redirect = qs.get('redirect') // 'https://dashboard.internal/'
const parsed = new URL(redirect!) // Bezpieczne do skonstruowania — już zdekodowane
console.log(parsed.hostname) // dashboard.internalBłąd 2 — Nie dekodowanie + jako spacji dla danych zakodowanych formularzami
Problem: Zgłoszenia formularzy i niektóre biblioteki OAuth kodują spacje jako + (application/x-www-form-urlencoded). Wywołanie decodeURIComponent() na tych danych pozostawia + jako dosłowny znak plus. Poprawka: używaj URLSearchParams do parsowania danych zakodowanych formularzami lub zastąp + spacją przed wywołaniem decodeURIComponent().
// ❌ decodeURIComponent traktuje + jako dosłowny plus, nie spację
// Endpoint tokenu OAuth wysyła: grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// Niektóre starsze implementacje OAuth używają też + dla spacji w kodach
const formBody = 'customer_name=Piotr+Kowalski&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // 'Piotr+Kowalski' ← + nie zamieniony na spację// ✅ Użyj URLSearchParams — przestrzega specyfikacji application/x-www-form-urlencoded
const formBody = 'customer_name=Piotr+Kowalski&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'Piotr Kowalski' ← + poprawnie zdekodowany jako spacja
console.log(params.get('product')) // 'Standing Desk Pro'Błąd 3 — Używanie decodeURI() dla poszczególnych wartości parametrów zapytania
Problem: decodeURI() nie dekoduje %26 (&), %3D (=) ani %3F (?) — znaków powszechnie kodowanych wewnątrz wartości parametrów. Używanie go do zdekodowania pojedynczej wartości pozostawia te sekwencje nienaruszone, po cichu produkując niepoprawne wyjście. Poprawka: używaj decodeURIComponent() dla poszczególnych wartości; zachowaj decodeURI() dla pełnych ciągów URL.
// ❌ decodeURI nie dekoduje & i = — wartość pozostaje uszkodzona const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURI(encodedFilter) console.log(filter) // 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' ← %3D i %26 nie zdekodowane
// ✅ decodeURIComponent dekoduje wszystkie sekwencje włącznie z & i = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← poprawnie zdekodowane
Błąd 4 — Nie owijanie decodeURIComponent w try/catch dla danych użytkownika
Problem: Sam znak %nie poprzedzony dwoma cyframi szesnastkowymi — powszechny w zapytaniach wyszukiwania wpisanych przez użytkowników (“50% zniżki”, “100% bawełna”) — powoduje że decodeURIComponent() zgłasza URIError: URI malformed, zawieszając obsługę żądania jeśli nie jest przechwycony. Poprawka: zawsze owijaj w try/catch gdy dane wejściowe pochodzą od użytkownika, fragmentów URL lub zewnętrznych systemów.
// ❌ Użytkownik wpisał '100% bawełna' w polu wyszukiwania — sam % zawiesza serwer
// GET /api/search?q=100%25+cotton ← Ten konkretny przypadek jest w porządku (%25 = %)
// GET /api/search?q=100%+cotton ← To zawiesza (% nie poprzedzony 2 cyframi szesnastkowymi)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ zgłasza URIError: URI malformed dla '100% bawełna' → nieobsłużony błąd 500
})// ✅ Owiń w try/catch — wróć do surowego wejścia jeśli dekodowanie się nie powiedzie
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // użyj surowej wartości zamiast zawieszać
}
// kontynuuj przetwarzanie bezpiecznie — query jest zdekodowane lub surowe
})decodeURIComponent vs decodeURI vs URLSearchParams — Szybkie Porównanie
| Metoda | Dekoduje %XX | Dekoduje + jako spację | Zgłasza przy błędzie | Dekoduje & = ? # | Przypadek użycia | Wymaga instalacji |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ wszystkie | ❌ nie | ✅ URIError | ✅ tak | Pojedyncze wartości i segmenty ścieżki | No |
| decodeURI() | ✅ większość | ❌ nie | ✅ URIError | ❌ nie | Pełne ciągi URL | No |
| URLSearchParams | ✅ wszystkie | ✅ tak | ❌ cicho | ✅ tak | Parsowanie ciągu zapytania z automatycznym dekodowaniem | No |
| Konstruktor URL | ✅ wszystkie | ✅ tak | ✅ TypeError | ✅ tak | Pełne parsowanie i normalizacja URL | No |
| decode-uri-component | ✅ wszystkie | ❌ nie | ❌ cicho | ✅ tak | Wsadowe dekodowanie z tolerancją zniekształconych danych | npm install |
| querystring.unescape() | ✅ wszystkie | ❌ nie | ❌ cicho | ✅ tak | Stary Node.js (przestarzałe w v16) | No (wbudowane) |
W zdecydowanej większości przypadków wybór sprowadza się do trzech scenariuszy. Używaj URLSearchParams do parsowania ciągów zapytania — obsługuje dekodowanie, regułę +-jako-spacja i powtarzające się klucze automatycznie. Używaj decodeURIComponent() (owinięty w try/catch) dla pojedynczej wartości lub segmentu ścieżki, szczególnie gdy spodziewasz się ukośników zakodowanych jako %2F wewnątrz segmentu. Używaj decodeURI() tylko gdy masz pełny ciąg URL ze znakami spoza ASCII w ścieżce i potrzebujesz aby znaki strukturalne (/ ? & =) pozostały zakodowane w wyjściu.
Często Zadawane Pytania
Powiązane Narzędzia
Aby jednym kliknięciem zdekodować bez pisania kodu, wklej swój ciąg zakodowany procentowo bezpośrednio do Dekodera URL ToolDeck — dekoduje natychmiast w przeglądarce, z wynikiem gotowym do skopiowania do kodu lub terminala.
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.