URL Decode JavaScript — decodeURIComponent()
Використовуйте безкоштовний URL Decode Online прямо в браузері — без встановлення.
Спробувати URL Decode Online онлайн →Рядки з відсотковим кодуванням постійно зустрічаються в JavaScript-коді — пошуковий запит надходить як q=standing+desk%26price%3A200, OAuth- перенаправлення як next=https%3A%2F%2Fdashboard.internal%2F, шлях до файлу як reports%2F2025%2Fq1.pdf. Декодування URL у JavaScript зводиться до вибору правильної з трьох вбудованих функцій: decodeURIComponent(), decodeURI() та URLSearchParams — і вибір між ними є головною причиною тихого пошкодження даних, яке я бачив у виробничих кодових базах, особливо крайній випадок +-як-пробіл та подвійне декодування. Для швидкого одноразового декодування без написання коду URL Decoder від ToolDeck впорається з цим миттєво в браузері. Цей підручник з декодування URL у JavaScript охоплює всі три функції детально (ES2015+ / Node.js 10+): коли використовувати кожну, як вони відрізняються для пробілів та зарезервованих символів, декодування з файлів та HTTP-запитів, безпечна обробка помилок і чотири помилки, що спричиняють найбільше прихованих виробничих проблем.
- ✓decodeURIComponent() декодує всі послідовності з відсотковим кодуванням — це правильний вибір для окремих значень параметрів запиту та сегментів шляху
- ✓decodeURI() зберігає структурні символи URI, як-от / ? & = # : — використовуйте його лише при декодуванні цілого рядка URL, ніколи для окремих значень
- ✓URLSearchParams.get() повертає вже декодовані значення — виклик decodeURIComponent() поверх нього спричиняє подвійне декодування
- ✓decodeURIComponent() НЕ декодує + як пробіл — для даних у форм-кодуванні (application/x-www-form-urlencoded) використовуйте URLSearchParams або замініть + перед декодуванням
- ✓Завжди загортайте decodeURIComponent() у try/catch, коли введення надходить від користувача — голий % або неповна послідовність кидає URIError
Що таке декодування URL?
Відсоткове кодування (формально визначене в RFC 3986) замінює символи, небезпечні або структурно значущі в URL, знаком % та двома шістнадцятковими цифрами — значенням байту UTF-8 символу. Декодування URL повертає це перетворення: кожна послідовність %XX перетворюється назад на вихідний байт, а отримана послідовність байтів інтерпретується як текст UTF-8. Пробіл декодується з %20, скісна риска з %2F, не-ASCII символ ü з %C3%BC (його двобайтове UTF-8 представлення).
Символи, які ніколи не кодуються — так звані незарезервовані символи — це букви A–Z та a–z, цифри 0–9 та - _ . ~. Все інше або має структурну роль в URL (як / для розділення сегментів шляху або & для розділення параметрів запиту), або має бути закодованим при використанні як значення даних. Практичний результат: пошуковий фільтр на зразок status=active&tier=premium, закодований як одне значення параметра запиту, виглядає зовсім інакше при отриманні.
// Закодовано відсотками — як отримано в HTTP-запиті або вебхук-навантаженні "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// Після декодування URL — початковий фільтр запиту Elasticsearch "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
decodeURIComponent() — Стандартна функція для декодування значень
decodeURIComponent() — це робоча конячка декодування URL у JavaScript. Вона декодує кожну послідовність %XX у вхідному рядку — включаючи символи зі структурним значенням у URL, як %2F (скісна риска), %3F (знак питання), %26 (амперсанд) та %3D (знак рівності). Це робить її правильним вибором для декодування окремих значень параметрів запиту та сегментів шляху, але неправильним для декодування цілого URL — де ці структурні символи мають залишатися закодованими. Це глобальна функція: жоден імпорт не потрібен у жодному JavaScript-середовищі.
Мінімальний робочий приклад
// Декодування окремих значень параметрів запиту — поширений випадок
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]'
// Примітка: + НЕ декодується як пробіл — дивіться розділ + у порівняльній таблиці
console.log(city) // São Paulo
console.log(district) // Itáim Bibi
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]Декодування URL перенаправлення, витягнутого з параметра запиту
// URL перенаправлення було закодовано відсотками, коли вбудовувалось як значення параметра
// сторона-одержувач: витягніть його, потім використайте — з 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') // Автоматично декодовано URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext вже декодовано: 'https://dashboard.internal/reports?view=weekly&team=platform'
// НЕ викликайте decodeURIComponent(rawNext) знову — це буде подвійне декодування
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Декодування не-ASCII та Unicode сегментів шляху
// REST API з інтернаціоналізованими сегментами шляху
// Кожен байт UTF-8 оригінального символу кодувався окремо
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (Токіо) — 3 байти на символ
'M%C3%BCnchen', // München — ü закодовано як 2 байти
'caf%C3%A9', // café — é як попередньо складений NFC
'S%C3%A3o%20Paulo', // São Paulo
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo
// Витягнення ключа файлу з URL сховища API
// Ключ об'єкта містив /, тому закодований як %2F всередині сегмента шляху
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname декодує кодування рівня URL, але %2F (як %252F на рівні URL) залишається
// Використовуйте decodeURIComponent для останнього кроку:
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() кидає URIError, коли вхідні дані містять %, за яким не слідують дві дійсні шістнадцяткові цифри — наприклад голий % в кінці рядка або послідовність на кшталт %GH. Завжди загортайте у блок try/catch при декодуванні введення від користувача. Безпечні шаблони декодування розглянуто в розділі Обробка помилок нижче.Функції декодування URL у JavaScript — Довідник символів
Три вбудовані функції декодування відрізняються тим, які саме закодовані послідовності вони декодують. Таблиця показує поведінку для символів, що найбільше важливі на практиці:
| Закодовано | Символ | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | пробіл | пробіл ✅ | пробіл ✅ | пробіл ✅ |
| + | плюс (форма) | + (збережено) | + (збережено) | пробіл ✅ |
| %2B | + літерал | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (збер.) ❌ | & ✅ |
| %3D | = | = ✅ | = (збер.) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (збер.) ❌ | ? ✅ |
| %23 | # | # ✅ | # (збер.) ❌ | # ✅ |
| %2F | / | / ✅ | / (збер.) ❌ | / ✅ |
| %3A | : | : ✅ | : (збер.) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (збер.) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
Два критичних рядки — це + та структурні символи (%26, %3D, %3F). URLSearchParams декодує + як пробіл, оскільки слідує специфікації application/x-www-form-urlencoded — правильно для HTML-форм, але відрізняється від поведінки decodeURIComponent() з тим самим символом. А decodeURI() тихо пропускає %26, %3D та %3F — що виглядає правильно, поки значення не містить закодований амперсанд або знак рівності.
decodeURI() — Декодування цілого URL без руйнування структури
decodeURI() є аналогом encodeURI(). Вона декодує цілий рядок URL, зберігаючи символи зі структурним значенням у URI: ; , / ? : @ & = + $ #. Вони залишаються у своїй закодованій формі (або як буквальні символи, якщо з'явилися незакодованими у вхідних даних). Це робить decodeURI() безпечною для санітизації повних URL, які можуть містити не-ASCII символи в шляху або імені хоста, не руйнуючи структуру рядка запиту.
Санітизація URL з не-ASCII сегментами шляху
// CDN URL з інтернаціоналізованими сегментами шляху та структурованим рядком запиту // decodeURI() декодує не-ASCII шлях, але зберігає ? & = незмінними 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 // ↑ Не-ASCII декодовано; ? & = збережено — URL залишається структурно дійсним // decodeURIComponent зруйнував би URL — : / ? & = декодуються всі одразу const broken = decodeURIComponent(encodedUrl) // Тут виглядає так само, але URL як 'https%3A%2F%2F...' буде зруйновано
URL перед decodeURI(), коли вам також потрібен доступ до окремих компонентів URL. new URL(str) нормалізує вхідні дані, перевіряє структуру та надає доступ до .pathname, .searchParams та .hostname як вже декодованих властивостей. Резервуйте decodeURI() для випадків, коли вам потрібен лише рядковий результат і ви не можете використати конструктор URL (наприклад, у дуже старих середовищах Node.js 6 без глобального класу URL).URLSearchParams — Автоматичне декодування для рядків запиту
URLSearchParams — ідіоматичний спосіб розбору рядків запиту в сучасному JavaScript. Кожне значення, що повертається методами .get(), .getAll() або ітерацією, автоматично декодується — включаючи + як пробіл для форм-кодованих даних. Він глобально доступний у всіх сучасних браузерах та Node.js 10+, не вимагає імпорту та обробляє крайні випадки, з якими ручне розбиття рядків помиляється.
Розбір вхідного рядка запиту
// Розбір вебхук-зворотного виклику або рядка запиту 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' ← + декодовано як пробіл
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A та %5B декодовано
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// Ітерація по всіх параметрах
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Розбір параметрів запиту з поточного URL браузера
// Поточний 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 (+ декодовано автоматично)
console.log(filters.category) // office furnitureДекодування даних з URL-кодуванням із файлів та відповідей API
У реальних проектах постійно виникають два сценарії: обробка файлу на диску, що містить дані з відсотковим кодуванням (журнали доступу, експорти даних, файли захоплених вебхуків), та розбір URL вхідного HTTP-запиту на Node.js-сервері. Обидва слідують одному принципу — використовуйте конструктор URL або URLSearchParams замість ручного розбиття рядків — але деталі відрізняються.
Читання та декодування записів з URL-кодуванням із файлу
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Файл: orders-export.txt — один запис з URL-кодуванням на рядок
// customer_name=%D0%9E%D0%BB%D0%B5%D0%BA%D1%81%D1%96%D0%B9+%D0%86%D0%B2%D0%B0%D0%BD%D0%BE%D0%B2&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=149.99%20EUR
// customer_name=%D0%9C%D0%B0%D1%80%D1%96%D1%8F+%D0%9A%D0%BE%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D0%BA%D0%BE&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=89.00%20EUR
// customer_name=%D0%9E%D0%BB%D0%B5%D0%BA%D1%81%D1%96%D0%B9+%D0%86%D0%B2%D0%B0%D0%BD%D0%BE%D0%B2&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 декодує + як пробіл та послідовності %XX автоматично
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'), // '149.99 EUR'
})
}
return orders
}
const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'Олексій Іванов', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '149.99 EUR' }Розбір журналу доступу Nginx для декодування пошукових запитів
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Рядки журналу доступу Nginx виглядають так:
// 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) {
// Витягнення шляху запиту з рядка журналу
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 декодує автоматично
} catch {
// Пропускати пошкоджені рядки — журнали доступу можуть містити скорочені записи
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']Розбір параметрів запиту в HTTP-сервері Node.js
import http from 'http'
// Вхідний URL: /api/products?q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D
const server = http.createServer((req, res) => {
// Побудова повного URL — другий аргумент є основою, необхідною для конструктора 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)Коли вам потрібно перевірити закодований URL під час розробки — щоб зрозуміти, що надсилає вебхук, перш ніж писати код розбору — вставте його безпосередньо у URL Decoder від ToolDeck щоб миттєво побачити декодований вигляд без запуску скрипту.
Декодування URL з командного рядка
Для shell-скриптів, CI-конвеєрів або швидкої одноразової перевірки закодованих рядків існує кілька підходів без написання повного скрипту. Однорядкові команди Node.js є кросплатформними; на macOS та Linux також завжди доступний python3.
# ── Однорядкові команди Node.js ──────────────────────────────────────────
# Декодування одного значення з відсотковим кодуванням
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio
# Розбір рядка запиту та виведення кожної пари ключ=значення (декодованої)
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
# Декодування та красивий вивід тіла JSON з URL-кодуванням (типово при налагодженні вебхуків)
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"
# }
# ── Однорядкова команда Python (доступна на більшості macOS/Linux систем) ─
# unquote_plus також декодує + як пробіл — правильно для форм-кодованих даних
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 — журналювання та декодування URL перенаправлення із заголовка відповіді ──────────
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))
"Толерантне декодування з decode-uri-component
Вбудована функція decodeURIComponent() кидає URIError при будь-якій пошкодженій послідовності — включаючи кінцевий % без двох наступних шістнадцяткових цифр. У виробничому коді, що обробляє URL, надані користувачами або третіми сторонами — журнали пошукових запитів, URL переходів із email-кампаній, зскрейпані веб-дані — пошкоджені послідовності досить поширені, щоб спричиняти збої. Пакет decode-uri-component (~30 млн тижневих завантажень npm) толерантно обробляє пошкоджені послідовності, повертаючи оригінальну послідовність без змін замість викидання винятку.
npm install decode-uri-component # або pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// Нативна функція кидає при пошкоджених вхідних даних — може призвести до збою обробника запиту
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('Нативна кинула:', (e as Error).message)
}
// decode-uri-component повертає сиру послідовність замість кидання
console.log(decodeUriComponent('product%name%'))
// product%name% ← пошкоджені послідовності залишені як є, без винятку
// Дійсні послідовності декодуються правильно
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio
// Змішано: дійсні послідовності декодовано, недійсні збережено
console.log(decodeUriComponent('%D0%9A%D0%B8%D1%97%D0%B2%20Office%20%ZZ%20HQ'))
// Київ Office %ZZ HQ ← %ZZ не є дійсним hex — залишено як є
// Практичне використання в конвеєрі обробки журналів
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← кидало б із decodeURIComponent (голий %)
'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 для коду програми, де ви хочете відразу знати про надходження несподіваного введення. Використовуйте decode-uri-component у конвеєрах даних, процесорах журналів та обробниках вебхуків, де ви хочете продовжувати обробку навіть коли деякі вхідні дані містять недійсне кодування.
Обробка пошкоджених рядків з відсотковим кодуванням
Голий %, неповна послідовність на зразок %A або недійсна пара на зразок %GH — все це призводить до того, що decodeURIComponent() кидає URIError: URI malformed. Будь-які введені дані від користувача — пошукові запити, фрагменти URL, поля форм із URL, параметри з посилань email-кампаній — можуть містити ці послідовності. Безпечна обгортка є необхідною для будь-якого зовнішньо орієнтованого коду.
Безпечні обгортки декодування для поширених сценаріїв
// ── 1. Базове безпечне декодування — повертає оригінальний рядок при помилці ──────────
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // повернути сирі вхідні дані при збої декодування — ніколи не падати
}
}
// ── 2. Безпечне декодування + обробка + як пробілу (для форм-кодованих значень) ────────
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // принаймні замінити + навіть якщо решта не вдасться
}
}
// ── 3. Безпечний повний парсер рядка запиту → простий об'єкт ────────────────────────
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 обробляє все декодування внутрішньо
}
} catch {
// Тихо повертати порожній об'єкт при повністю пошкоджених вхідних даних
}
return result
}
// Використання
console.log(safeDecode('S%C3%A3o%20Paulo')) // São Paulo
console.log(safeDecode('search%20for%2050%25+off')) // search for 50% off (голий %)
// → насправді добре; % тут є %25
console.log(safeDecode('malformed%string%')) // malformed%string% (без винятку)
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' }Поширені помилки
Я бачив, як ці чотири шаблони спричиняли тихе пошкодження даних або несподівані збої у виробництві — зазвичай лише коли значення випадково містить спеціальний символ, що означає, що вони проходять через модульні тести і виявляються лише з реальними користувацькими даними.
Помилка 1 — Подвійне декодування значення URLSearchParams
Проблема: URLSearchParams.get() повертає вже декодований рядок. Виклик decodeURIComponent() поверх нього подвійно декодує значення — перетворюючи будь-які символи % у декодованому виводі на %25, що пошкоджує дані. Виправлення: використовуйте значення з URLSearchParams.get() безпосередньо — додаткове декодування не потрібне.
// ❌ URLSearchParams вже декодував — повторне декодування пошкоджує значення з %
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← вже декодовано
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← % декодовано до %25 під час другого проходу — тепер знову неправильно// ✅ Використовуйте URLSearchParams.get() безпосередньо — декодування автоматичне
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!) // Безпечно конструювати — вже декодовано
console.log(parsed.hostname) // dashboard.internalПомилка 2 — Не декодувати + як пробіл для форм-кодованих даних
Проблема: Форм-відправки та деякі OAuth-бібліотеки кодують пробіли як + (application/x-www-form-urlencoded). Виклик decodeURIComponent() на цих даних залишає + як буквальний знак плюс. Виправлення: використовуйте URLSearchParams для розбору форм-кодованих даних, або замініть + на пробіл перед викликом decodeURIComponent().
// ❌ decodeURIComponent трактує + як буквальний плюс, не пробіл
// OAuth token endpoint надсилає: grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// Деякі застарілі OAuth-реалізації також використовують + для пробілів у кодах
const formBody = 'customer_name=%D0%9E%D0%BB%D0%B5%D0%BA%D1%81%D1%96%D0%B9+%D0%86%D0%B2%D0%B0%D0%BD%D0%BE%D0%B2&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // 'Олексій+Іванов' ← + не перетворено на пробіл// ✅ Використовуйте URLSearchParams — слідує специфікації application/x-www-form-urlencoded
const formBody = 'customer_name=%D0%9E%D0%BB%D0%B5%D0%BA%D1%81%D1%96%D0%B9+%D0%86%D0%B2%D0%B0%D0%BD%D0%BE%D0%B2&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'Олексій Іванов' ← + правильно декодовано як пробіл
console.log(params.get('product')) // 'Standing Desk Pro'Помилка 3 — Використання decodeURI() для окремих значень параметрів запиту
Проблема: decodeURI() не декодує %26 (&), %3D (=) або %3F (?) — символи, що зазвичай закодовані всередині значень параметрів. Використання для декодування одного значення залишає ці послідовності нетронутими і тихо виробляє неправильний вивід. Виправлення: використовуйте decodeURIComponent() для окремих значень; резервуйте decodeURI() для повних рядків URL.
// ❌ decodeURI не декодує & та = — значення залишається пошкодженим 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 не декодовано
// ✅ decodeURIComponent декодує всі послідовності, включаючи & та = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← правильно декодовано
Помилка 4 — Не загортати decodeURIComponent у try/catch для введення від користувача
Проблема: Голий %без двох шістнадцяткових цифр — поширений у пошукових запитах, введених користувачами (“50% знижки”, “100% бавовна”) — змушує decodeURIComponent() кидати URIError: URI malformed і аварійно завершувати обробник запиту, якщо не спіймано. Виправлення: завжди загортайте у блок try/catch, коли введення походить від користувацьких даних, фрагментів URL або зовнішніх систем.
// ❌ Користувач ввів '100% бавовна' у поле пошуку — голий % призводить до збою сервера
// GET /api/search?q=100%25+%D0%B1%D0%B0%D0%B2%D0%BE%D0%B2%D0%BD%D0%B0 ← Цей конкретний випадок нормальний (%25 = %)
// GET /api/search?q=100%+%D0%B1%D0%B0%D0%B2%D0%BE%D0%B2%D0%BD%D0%B0 ← Це призведе до збою (% не слідують 2 hex цифри)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ кидає URIError: URI malformed для '100% бавовна' → необроблена помилка 500
})// ✅ Загортайте у try/catch — відкат до сирих даних при збої декодування
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // використовувати сире значення замість збою
}
// продовжувати безпечну обробку — query або декодовано, або сирі дані
})decodeURIComponent vs decodeURI vs URLSearchParams — Швидке порівняння
| Метод | Декодує %XX | Декодує + як пробіл | Кидає при пошкодженому введенні | Декодує & = ? # | Випадок використання | Потребує встановлення |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ усі | ❌ ні | ✅ URIError | ✅ так | Окремі значення та сегменти шляху | Ні |
| decodeURI() | ✅ більшість | ❌ ні | ✅ URIError | ❌ ні | Повні рядки URL | Ні |
| URLSearchParams | ✅ усі | ✅ так | ❌ тихо | ✅ так | Розбір рядка запиту з автоматичним декодуванням | Ні |
| URL конструктор | ✅ усі | ✅ так | ✅ TypeError | ✅ так | Повний розбір та нормалізація URL | Ні |
| decode-uri-component | ✅ усі | ❌ ні | ❌ тихо | ✅ так | Пакетне декодування з толерантністю до помилок | npm install |
| querystring.unescape() | ✅ усі | ❌ ні | ❌ тихо | ✅ так | Застарілий Node.js (deprecated з v16) | Ні (вбудований) |
У переважній більшості випадків вибір зводиться до трьох сценаріїв. Використовуйте URLSearchParams для розбору рядків запиту — він автоматично обробляє декодування, правило +-як-пробіл та повторювані ключі. Використовуйте decodeURIComponent() (загорнутий у блок try/catch) для одного значення або сегмента шляху, особливо коли ви очікуєте скісні риски, закодовані як %2F, всередині сегмента. Використовуйте decodeURI() лише тоді, коли у вас є повний рядок URL з не-ASCII символами в шляху і структурні символи (/ ? & =) мають залишатися закодованими у виводі.
Часті запитання
Пов'язані інструменти
Для декодування в один клік без написання будь-якого коду вставте свій рядок з відсотковим кодуванням безпосередньо у URL Decoder від ToolDeck — він миттєво декодує у браузері, а результат готовий до копіювання у ваш код або термінал.
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.