JSON ( JavaScript Object Notation), is widely used format for asynchronous communication between webpage or mobile application to back-end servers. Due to increasing trend in Single Page Application or Mobile Application, popularity of the JSON is extreme.
Why do we get JSON parse error?
Parsing JSON is a very common task in JavaScript. JSON.parse() is a built-in method in JavaScript which is used to parse a JSON string and convert it into a JavaScript object. If the JSON string is invalid, it will throw a SyntaxError.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
How to handle JSON parse error?
There are many ways to handle JSON parse error. In this post, I will show you how to handle JSON parse error in JavaScript.
1. Using try-catch block
The most common way to handle JSON parse error is using try-catch block. If the JSON string is valid, it will return a JavaScript object. If the JSON string is invalid, it will throw a SyntaxError.
try {
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
} catch (e) {
console.log(e);
// expected output: SyntaxError: Unexpected token o in JSON at position 1
}
2. Using if-else block
Another way to handle JSON parse error is using if-else block.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
if (obj instanceof SyntaxError) {
console.log(obj);
// expected output: SyntaxError: Unexpected token o in JSON at position 1
} else {
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
}
3. Using try-catch block with JSON.parse()
The third way to handle JSON parse error is using try-catch block with JSON.parse().
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json, (key, value) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
});
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
4. Using try-catch block with JSON.parse() and JSON.stringify()
The fourth way to handle JSON parse error is using try-catch block with JSON.parse() and JSON.stringify(). If the JSON string is valid, it will return a JavaScript object. If the JSON string is invalid, it will return a SyntaxError.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json, (key, value) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
});
const str = JSON.stringify(obj);
console.log(str);
// expected output: {"result":true,"count":42}
Обработка ошибок, «try..catch»
Неважно, насколько мы хороши в программировании, иногда наши скрипты содержат ошибки. Они могут возникать из-за наших промахов, неожиданного ввода пользователя, неправильного ответа сервера и по тысяче других причин.
Обычно скрипт в случае ошибки «падает» (сразу же останавливается), с выводом ошибки в консоль.
Но есть синтаксическая конструкция try..catch
, которая позволяет «ловить» ошибки и вместо падения делать что-то более осмысленное.
Синтаксис «try..catch»
Конструкция try..catch
состоит из двух основных блоков: try
, и затем catch
:
try { // код... } catch (err) { // обработка ошибки }
Работает она так:
- Сначала выполняется код внутри блока
try {...}
. - Если в нём нет ошибок, то блок
catch(err)
игнорируется: выполнение доходит до концаtry
и потом далее, полностью пропускаяcatch
. - Если же в нём возникает ошибка, то выполнение
try
прерывается, и поток управления переходит в началоcatch(err)
. Переменнаяerr
(можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в блоке try {…}
скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch
.
Давайте рассмотрим примеры.
-
Пример без ошибок: выведет
alert
(1)
и(2)
:try { alert('Начало блока try'); // *!*(1) <--*/!* // ...код без ошибок alert('Конец блока try'); // *!*(2) <--*/!* } catch(err) { alert('Catch игнорируется, так как нет ошибок'); // (3) }
-
Пример с ошибками: выведет
(1)
и(3)
:try { alert('Начало блока try'); // *!*(1) <--*/!* *!* lalala; // ошибка, переменная не определена! */!* alert('Конец блока try (никогда не выполнится)'); // (2) } catch(err) { alert(`Возникла ошибка!`); // *!*(3) <--*/!* }
««warn header=»try..catch
работает только для ошибок, возникающих во время выполнения кода»
Чтобы `try..catch` работал, код должен быть выполнимым. Другими словами, это должен быть корректный JavaScript-код.
Он не сработает, если код синтаксически неверен, например, содержит несовпадающее количество фигурных скобок:
try { {{{{{{{{{{{{ } catch(e) { alert("Движок не может понять этот код, он некорректен"); }
JavaScript-движок сначала читает код, а затем исполняет его. Ошибки, которые возникают во время фазы чтения, называются ошибками парсинга. Их нельзя обработать (изнутри этого кода), потому что движок не понимает код.
Таким образом, try..catch
может обрабатывать только ошибки, которые возникают в корректном коде. Такие ошибки называют «ошибками во время выполнения», а иногда «исключениями».
````warn header="`try..catch` работает синхронно"
Исключение, которое произойдёт в коде, запланированном "на будущее", например в `setTimeout`, `try..catch` не поймает:
```js run
try {
setTimeout(function() {
noSuchVariable; // скрипт упадёт тут
}, 1000);
} catch (e) {
alert( "не сработает" );
}
```
Это потому, что функция выполняется позже, когда движок уже покинул конструкцию `try..catch`.
Чтобы поймать исключение внутри запланированной функции, `try..catch` должен находиться внутри самой этой функции:
```js run
setTimeout(function() {
try {
noSuchVariable; // try..catch обрабатывает ошибку!
} catch {
alert( "ошибка поймана!" );
}
}, 1000);
```
Объект ошибки
Когда возникает ошибка, JavaScript генерирует объект, содержащий её детали. Затем этот объект передаётся как аргумент в блок catch
:
try { // ... } catch(err) { // <-- объект ошибки, можно использовать другое название вместо err // ... }
Для всех встроенных ошибок этот объект имеет два основных свойства:
name
: Имя ошибки. Например, для неопределённой переменной это "ReferenceError"
.
message
: Текстовое сообщение о деталях ошибки.
В большинстве окружений доступны и другие, нестандартные свойства. Одно из самых широко используемых и поддерживаемых — это:
stack
: Текущий стек вызова: строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки.
Например:
try { *!* lalala; // ошибка, переменная не определена! */!* } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...стек вызовов) // Можем также просто вывести ошибку целиком // Ошибка приводится к строке вида "name: message" alert(err); // ReferenceError: lalala is not defined }
Блок «catch» без переменной
[recent browser=new]
Если нам не нужны детали ошибки, в catch
можно её пропустить:
try { // ... } catch { // <-- без (err) // ... }
Использование «try..catch»
Давайте рассмотрим реальные случаи использования try..catch
.
Как мы уже знаем, JavaScript поддерживает метод JSON.parse(str) для чтения JSON.
Обычно он используется для декодирования данных, полученных по сети, от сервера или из другого источника.
Мы получаем их и вызываем JSON.parse
вот так:
let json = '{"name":"John", "age": 30}'; // данные с сервера *!* let user = JSON.parse(json); // преобразовали текстовое представление в JS-объект */!* // теперь user - объект со свойствами из строки alert( user.name ); // John alert( user.age ); // 30
Вы можете найти более детальную информацию о JSON в главе info:json.
Если json
некорректен, JSON.parse
генерирует ошибку, то есть скрипт «падает».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень не любят, когда что-то «просто падает» без всякого сообщения об ошибке.
Давайте используем try..catch
для обработки ошибки:
let json = "{ некорректный JSON }"; try { *!* let user = JSON.parse(json); // <-- тут возникает ошибка... */!* alert( user.name ); // не сработает } catch (e) { *!* // ...выполнение прыгает сюда alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз." ); alert( e.name ); alert( e.message ); */!* }
Здесь мы используем блок catch
только для вывода сообщения, но мы также можем сделать гораздо больше: отправить новый сетевой запрос, предложить посетителю альтернативный способ, отослать информацию об ошибке на сервер для логирования, … Всё лучше, чем просто «падение».
Генерация собственных ошибок
Что если json
синтаксически корректен, но не содержит необходимого свойства name
?
Например, так:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); // <-- выполнится без ошибок *!* alert( user.name ); // нет свойства name! */!* } catch (e) { alert( "не выполнится" ); }
Здесь JSON.parse
выполнится без ошибок, но на самом деле отсутствие свойства name
для нас ошибка.
Для того, чтобы унифицировать обработку ошибок, мы воспользуемся оператором throw
.
Оператор «throw»
Оператор throw
генерирует ошибку.
Синтаксис:
Технически в качестве объекта ошибки можно передать что угодно. Это может быть даже примитив, число или строка, но всё же лучше, чтобы это был объект, желательно со свойствами name
и message
(для совместимости со встроенными ошибками).
В JavaScript есть множество встроенных конструкторов для стандартных ошибок: Error
, SyntaxError
, ReferenceError
, TypeError
и другие. Можно использовать и их для создания объектов ошибки.
Их синтаксис:
let error = new Error(message); // или let error = new SyntaxError(message); let error = new ReferenceError(message); // ...
Для встроенных ошибок (не для любых объектов, только для ошибок), свойство name
— это в точности имя конструктора. А свойство message
берётся из аргумента.
Например:
let error = new Error(" Ого, ошибка! o_O"); alert(error.name); // Error alert(error.message); // Ого, ошибка! o_O
Давайте посмотрим, какую ошибку генерирует JSON.parse
:
try { JSON.parse("{ bad json o_O }"); } catch(e) { *!* alert(e.name); // SyntaxError */!* alert(e.message); // Unexpected token b in JSON at position 2 }
Как мы видим, это SyntaxError
.
В нашем случае отсутствие свойства name
— это ошибка, ведь пользователи должны иметь имена.
Сгенерируем её:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); // <-- выполнится без ошибок if (!user.name) { *!* throw new SyntaxError("Данные неполны: нет имени"); // (*) */!* } alert( user.name ); } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: Данные неполны: нет имени }
В строке (*)
оператор throw
генерирует ошибку SyntaxError
с сообщением message
. Точно такого же вида, как генерирует сам JavaScript. Выполнение блока try
немедленно останавливается, и поток управления прыгает в catch
.
Теперь блок catch
становится единственным местом для обработки всех ошибок: и для JSON.parse
и для других случаев.
Проброс исключения
В примере выше мы использовали try..catch
для обработки некорректных данных. А что, если в блоке try {...}
возникнет другая неожиданная ошибка? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными.
Пример:
let json = '{ "age": 30 }'; // данные неполны try { user = JSON.parse(json); // <-- забыл добавить "let" перед user // ... } catch(err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (не JSON ошибка на самом деле) }
Конечно, возможно все! Программисты совершают ошибки. Даже в утилитах с открытым исходным кодом, используемых миллионами людей на протяжении десятилетий — вдруг может быть обнаружена ошибка, которая приводит к ужасным взломам.
В нашем случае try..catch
предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе catch
получает все свои ошибки из try
. Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение "JSON Error"
. Это неправильно и затрудняет отладку кода.
К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству name
:
try { user = { /*...*/ }; } catch(e) { *!* alert(e.name); // "ReferenceError" из-за неопределённой переменной */!* }
Есть простое правило:
Блок catch
должен обрабатывать только те ошибки, которые ему известны, и «пробрасывать» все остальные.
Техника «проброс исключения» выглядит так:
- Блок
catch
получает все ошибки. - В блоке
catch(err) {...}
мы анализируем объект ошибкиerr
. - Если мы не знаем как её обработать, тогда делаем
throw err
.
В коде ниже мы используем проброс исключения, catch
обрабатывает только SyntaxError
:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Данные неполны: нет имени"); } *!* blabla(); // неожиданная ошибка */!* alert( user.name ); } catch(e) { *!* if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { throw e; // проброс (*) } */!* }
Ошибка в строке (*)
из блока catch
«выпадает наружу» и может быть поймана другой внешней конструкцией try..catch
(если есть), или «убьёт» скрипт.
Таким образом, блок catch
фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные.
Пример ниже демонстрирует, как такие ошибки могут быть пойманы с помощью ещё одного уровня try..catch
:
function readData() { let json = '{ "age": 30 }'; try { // ... *!* blabla(); // ошибка! */!* } catch (e) { // ... if (e.name != 'SyntaxError') { *!* throw e; // проброс исключения (не знаю как это обработать) */!* } } } try { readData(); } catch (e) { *!* alert( "Внешний catch поймал: " + e ); // поймал! */!* }
Здесь readData
знает только, как обработать SyntaxError
, тогда как внешний блок try..catch
знает, как обработать всё.
try..catch..finally
Подождите, это ещё не всё.
Конструкция try..catch
может содержать ещё одну секцию: finally
.
Если секция есть, то она выполняется в любом случае:
- после
try
, если не было ошибок, - после
catch
, если ошибки были.
Расширенный синтаксис выглядит следующим образом:
*!*try*/!* { ... пробуем выполнить код... } *!*catch*/!*(e) { ... обрабатываем ошибки ... } *!*finally*/!* { ... выполняем всегда ... }
Попробуйте запустить такой код:
try { alert( 'try' ); if (confirm('Сгенерировать ошибку?')) BAD_CODE(); } catch (e) { alert( 'catch' ); } finally { alert( 'finally' ); }
У кода есть два пути выполнения:
- Если вы ответите на вопрос «Сгенерировать ошибку?» утвердительно, то
try -> catch -> finally
. - Если ответите отрицательно, то
try -> finally
.
Секцию finally
часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет.
Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи fib(n)
. Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация fib(n)
в коде ниже возвращает ошибку для отрицательных и для нецелых чисел.
Секция finally
отлично подходит для завершения измерений несмотря ни на что.
Здесь finally
гарантирует, что время будет измерено корректно в обеих ситуациях — и в случае успешного завершения fib
и в случае ошибки:
let num = +prompt("Введите положительное целое число?", 35) let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Должно быть целое неотрицательное число"); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); try { result = fib(num); } catch (e) { result = 0; *!* } finally { diff = Date.now() - start; } */!* alert(result || "возникла ошибка"); alert( `Выполнение заняло ${diff}ms` );
Вы можете это проверить, запустив этот код и введя 35
в prompt
— код завершится нормально, finally
выполнится после try
. А затем введите -1
— незамедлительно произойдёт ошибка, выполнение займёт 0ms
. Оба измерения выполняются корректно.
Другими словами, неважно как завершилась функция: через return
или throw
. Секция finally
срабатывает в обоих случаях.
«`smart header=»Переменные внутри try..catch..finally
локальны»
Обратите внимание, что переменные `result` и `diff` в коде выше объявлены до `try..catch`.
Если переменную объявить в блоке, например, в try
, то она не будет доступна после него.
````smart header="`finally` и `return`"
Блок `finally` срабатывает при *любом* выходе из `try..catch`, в том числе и `return`.
В примере ниже из `try` происходит `return`, но `finally` получает управление до того, как контроль возвращается во внешний код.
```js run
function func() {
try {
*!*
return 1;
*/!*
} catch (e) {
/* ... */
} finally {
*!*
alert( 'finally' );
*/!*
}
}
alert( func() ); // сначала срабатывает alert из finally, а затем этот код
````smart header="`try..finally`"
Конструкция `try..finally` без секции `catch` также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились.
```js
function func() {
// начать делать что-то, что требует завершения (например, измерения)
try {
// ...
} finally {
// завершить это, даже если все упадёт
}
}
```
В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока `catch`. Но `finally` отрабатывает до того, как поток управления выйдет из функции.
Глобальный catch
Информация из данной секции не является частью языка JavaScript.
Давайте представим, что произошла фатальная ошибка (программная или что-то ещё ужасное) снаружи try..catch
, и скрипт упал.
Существует ли способ отреагировать на такие ситуации? Мы можем захотеть залогировать ошибку, показать что-то пользователю (обычно они не видят сообщение об ошибке) и т.д.
Такого способа нет в спецификации, но обычно окружения предоставляют его, потому что это весьма полезно. Например, в Node.js для этого есть process.on("uncaughtException")
. А в браузере мы можем присвоить функцию специальному свойству window.onerror, которая будет вызвана в случае необработанной ошибки.
Синтаксис:
window.onerror = function(message, url, line, col, error) { // ... };
message
: Сообщение об ошибке.
url
: URL скрипта, в котором произошла ошибка.
line
, col
: Номера строки и столбца, в которых произошла ошибка.
error
: Объект ошибки.
Пример:
<script> *!* window.onerror = function(message, url, line, col, error) { alert(`${message}n В ${line}:${col} на ${url}`); }; */!* function readData() { badFunc(); // Ой, что-то пошло не так! } readData(); </script>
Роль глобального обработчика window.onerror
обычно заключается не в восстановлении выполнения скрипта — это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам.
Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как https://errorception.com или http://www.muscula.com.
Они работают так:
- Мы регистрируемся в сервисе и получаем небольшой JS-скрипт (или URL скрипта) от них для вставки на страницы.
- Этот JS-скрипт ставит свою функцию
window.onerror
. - Когда возникает ошибка, она выполняется и отправляет сетевой запрос с информацией о ней в сервис.
- Мы можем войти в веб-интерфейс сервиса и увидеть ошибки.
Итого
Конструкция try..catch
позволяет обрабатывать ошибки во время исполнения кода. Она позволяет запустить код и перехватить ошибки, которые могут в нём возникнуть.
Синтаксис:
try { // исполняем код } catch(err) { // если случилась ошибка, прыгаем сюда // err - это объект ошибки } finally { // выполняется всегда после try/catch }
Секций catch
или finally
может не быть, то есть более короткие конструкции try..catch
и try..finally
также корректны.
Объекты ошибок содержат следующие свойства:
message
— понятное человеку сообщение.name
— строка с именем ошибки (имя конструктора ошибки).stack
(нестандартное, но хорошо поддерживается) — стек на момент ошибки.
Если объект ошибки не нужен, мы можем пропустить его, используя catch {
вместо catch(err) {
.
Мы можем также генерировать собственные ошибки, используя оператор throw
. Аргументом throw
может быть что угодно, но обычно это объект ошибки, наследуемый от встроенного класса Error
. Подробнее о расширении ошибок см. в следующей главе.
Проброс исключения — это очень важный приём обработки ошибок: блок catch
обычно ожидает и знает, как обработать определённый тип ошибок, поэтому он должен пробрасывать дальше ошибки, о которых он не знает.
Даже если у нас нет try..catch
, большинство сред позволяют настроить «глобальный» обработчик ошибок, чтобы ловить ошибки, которые «выпадают наружу». В браузере это window.onerror
.
JSON parsing is the process of converting a JSON object in text format to a Javascript object that can be used inside a program. In Javascript, the standard way to do this is by using the method JSON.parse()
, as the Javascript standard specifies.
Using JSON.parse()
Javascript programs can read JSON objects from a variety of sources, but the most common sources are databases or REST APIs. A JSON object read from these sources enters a Javascript program in a “flat” string format that contains the object’s key-value pairs.
To create a valid string variable, you must surround the JSON string with single quotes.
let flatJSON = '{"a": "b", "c": 1, "d": {"e": 2}}';
JSON.parse()
requires one parameter: the flat JSON string to be converted into a JSON object. JSON.parse()
takes the flat JSON string directly or as a Javascript variable. Passing variables is easier and more readable than passing in a long flat JSON string.
JSON.parse('{"a", "b", "c", 1}');
let flatJSON2 = '{"a": "b", "c": 1, "d": {"e": 2}}';
JSON.parse(flatJSON2);
JSON.parse()
takes an optional second parameter which is called a “reviver.” A reviver is a function that converts the JSON data that JSON.parse()
cannot process by itself. This reviver function example handles undefined values:
let flatJSON3 = ‘{“a”: “b”, “c”: 1, “d”: {“e”: 2}}’;
let reviver = function(key, value) {
if(typeof value === 'undefined') { return null; }
;
let parsed = JSON.parse(flatJSON3, reviver);
Revivers convert empty values and complex objects into valid Javascript objects. The next section goes into the specifics of how this is done with common examples.
JSON.parse()
itself cannot execute functions or perform calculations. JSON objects can only hold simple data types and not executable code.
If you force code into a JSON object with a string, you must use the Javascript eval()
function to convert it into something the Javascript interpreter understands. This process is prone to error and often causes problems with the converted code’s scope.
Revivers have the specific purpose of converting string data into valid Javascript types. You should make conversions within reviver functions as simple as possible. In rare cases, a specialized conversion might require a lot of calculations.
Handling JSON.parse() Special Cases with Revivers
Error Handling
Improperly-formatted data passed to JSON.parse()
raises an error, stops processing, and returns no processed data, even if the rest of the JSON is correct. If an error occurs, never assume that JSON.parse()
returns a specific value.
Depending on how you write your program, an error could stop the rest of your Javascript code from executing. Wrap calls to JSON.parse()
in a try catch statement so you can explicitly specify what happens if parsing fails.
try {
JSON.parse(input);
} catch (e) {
return undefined; // Or whatever action you want here
}
Array Data
JSON.parse()
converts array data into a Javascript array. The array data must be a valid JSON string.
This is an uncommon use for JSON.parse()
. It’s easier to declare an array in code instead of going through JSON first. Declare arrays inside your Javascript program whenever possible.
Dates
Dates are not a valid JSON type. Converting strings of a particular format into a Javascript date type is a common extra step after using JSON.parse()
. Several examples are shown below.
A reviver function that converts strings to dates may look like this, if using ISO standard date format:
function reviver(key, value) {
if (value.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)) {
return new Date(value);
}
}
The Javascript Date
object translates various string formats into dates. Always check whether the resulting date makes sense, even if the Date
object “understood” its format. Here’s an example of a string conversion that results in an invalid real-world date:
// Converted to valid date: 02/16/2021
let date1 = JSON.parse('{"test": "2021-02-16"}');
// Converted to invalid date: 02/30/2021
let date2 = JSON.parse('{"test": "2021-02-30"}');
Empty Values
Null values and empty strings (“”) are valid JSON values, but the state of being undefined is not valid within JSON. JSON.parse()
will fail if it encounters an undefined value.
Searching for undefined values in a JSON string is a possible workaround for this problem, but be careful not to match any instances of the word “undefined” in a string. A better solution is to search for and replace the undefined value with a similar empty value type:
let parsedVal = (typeof value === 'undefined') ? null : value;
Using JSON.parse() Safely
Wrap JSON.parse()
in a function to cover its limits, handing special cases appropriate to your needs. Include a reviver function in a closure to handle common data conversion needs.
When you need more specialized data conversion than your default reviver function can handle, add a second reviver function as a parameter to your safe parsing function. Call the second reviver function at the end of the default reviver function.
Putting all of this together with previous code examples, here’s a simple safe JSON parsing function:
function safeJsonParse(json, reviver) {
function defaultReviver(key, value) {
if (value.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)) {
return new Date(value);
}
if(typeof value === 'undefined') { return null; }
if(reviver !== undefined) { reviver(); }
}
try {
let json = JSON.parse(json, defaultReviver);
} catch(e) {
return null;
}
}
Conclusion
JSON.parse()
is a crucial method for converting JSON data in string form into Javascript objects. It is possible to convert simple or complex objects, but you should never convert calculations or code, like for loops.
Enroll in our Intro to Programming Nanodegree Program today to learn more about JSON parsing with JSON.parse()
and other programming concepts.
Start Learning
A refresher on the purpose and syntax of JSON, as well as a detailed exploration of the JSON Parse SyntaxError in JavaScript.
Traveling deftly through to the next item in our JavaScript Error Handling series, today we’re taking a hard look at the JSON Parse
error. The JSON Parse
error, as the name implies, surfaces when using the JSON.parse()
method that fails to pass valid JSON as an argument.
In this article, we’ll dig deeper into where JSON Parse
errors sit in the JavaScript error hierarchy, as well as when it might appear and how to handle it when it does. Let’s get started!
The Technical Rundown
- All JavaScript error objects are descendants of the
Error
object, or an inherited object therein. - The
SyntaxError
object is inherited from theError
object. - The
JSON Parse
error is a specific type ofSyntaxError
object.
When Should You Use It?
While most developers are probably intimately familiar with JSON and the proper formatting syntax it requires, it doesn’t hurt to briefly review it ourselves, to better understand some common causes of the JSON Parse
error in JavaScript.
JavaScript Object Notation
, better known as JSON
, is a human-readable text format, commonly used to transfer data across the web. The basic structure of JSON consists of objects
, which are sets of string: value
pairs surrounded by curly braces:
{
"first": "Jane",
"last": "Doe"
}
An array
is a set of values
, surrounded by brackets:
[
"Jane",
"Doe"
]
A value
can be a string
, number
, object
, array
, boolean
, or null
.
That’s really all there is to the JSON syntax. Since values
can be other objects
or arrays
, JSON can be infinitely nested (theoretically).
In JavaScript, when passing JSON to the JSON.parse()
method, the method expects properly formatted JSON as the first argument. When it detects invalid JSON, it throws a JSON Parse
error.
For example, one of the most common typos or syntax errors in JSON is adding an extra comma separator at the end of an array
or object
value
set. Notice in the first few examples above, we only use a comma to literally separate values
from one another. Here we’ll try adding an extra, or «hanging», comma after our final value
:
var printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}
try {
var json = `
{
«first»: «Jane»,
«last»: «Doe»,
}
`
console.log(JSON.parse(json));
} catch (e) {
if (e instanceof SyntaxError) {
printError(e, true);
} else {
printError(e, false);
}
}
Note: We’re using the backtick (`
) string syntax to initialize our JSON, which just allows us to present it in a more readable form. Functionally, this is identical to a string that is contained on a single line.
As expected, our extraneous comma at the end throws a JSON Parse
error:
[EXPLICIT] SyntaxError: Unexpected token } in JSON at position 107
In this case, it’s telling us the }
token is unexpected, because the comma at the end informs JSON that there should be a third value
to follow.
Another common syntax issue is neglecting to surround string
values within string: value
pairs with quotations ("
). Many other language syntaxes use similar key: value
pairings to indicate named arguments and the like, so developers may find it easy to forget that JSON requires the string to be explicitly indicated using quotation marks:
var printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}
try {
var json = `
{
«first»: «Jane»,
last: «Doe»,
}
`
console.log(JSON.parse(json));
} catch (e) {
if (e instanceof SyntaxError) {
printError(e, true);
} else {
printError(e, false);
}
}
Here we forgot quotations around the "last"
key string
, so we get another JSON Parse
error:
[EXPLICIT] SyntaxError: Unexpected token l in JSON at position 76
A few examples are probably sufficient to see how the JSON Parse
error comes about, but as it so happens, there are dozens of possible versions of this error, depending on how JSON was improperly formatted. Here’s the full list:
JSON Parse Error Messages |
---|
SyntaxError: JSON.parse: unterminated string literal |
SyntaxError: JSON.parse: bad control character in string literal |
SyntaxError: JSON.parse: bad character in string literal |
SyntaxError: JSON.parse: bad Unicode escape |
SyntaxError: JSON.parse: bad escape character |
SyntaxError: JSON.parse: unterminated string |
SyntaxError: JSON.parse: no number after minus sign |
SyntaxError: JSON.parse: unexpected non-digit |
SyntaxError: JSON.parse: missing digits after decimal point |
SyntaxError: JSON.parse: unterminated fractional number |
SyntaxError: JSON.parse: missing digits after exponent indicator |
SyntaxError: JSON.parse: missing digits after exponent sign |
SyntaxError: JSON.parse: exponent part is missing a number |
SyntaxError: JSON.parse: unexpected end of data |
SyntaxError: JSON.parse: unexpected keyword |
SyntaxError: JSON.parse: unexpected character |
SyntaxError: JSON.parse: end of data while reading object contents |
SyntaxError: JSON.parse: expected property name or ‘}’ |
SyntaxError: JSON.parse: end of data when ‘,’ or ‘]’ was expected |
SyntaxError: JSON.parse: expected ‘,’ or ‘]’ after array element |
SyntaxError: JSON.parse: end of data when property name was expected |
SyntaxError: JSON.parse: expected double-quoted property name |
SyntaxError: JSON.parse: end of data after property name when ‘:’ was expected |
SyntaxError: JSON.parse: expected ‘:’ after property name in object |
SyntaxError: JSON.parse: end of data after property value in object |
SyntaxError: JSON.parse: expected ‘,’ or ‘}’ after property value in object |
SyntaxError: JSON.parse: expected ‘,’ or ‘}’ after property-value pair in object literal |
SyntaxError: JSON.parse: property names must be double-quoted strings |
SyntaxError: JSON.parse: expected property name or ‘}’ |
SyntaxError: JSON.parse: unexpected character |
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data |
To dive even deeper into understanding how your applications deal with JavaScript Errors, check out the revolutionary Airbrake JavaScript
error tracking tool for real-time alerts and instantaneous insight into what went wrong with your JavaScript code.
An Easier Way to Find JavaScript Errors
The first way to find a JSON Parse error is to go through your logs, but when you’re dealing with hundreds, if not thousands, of lines of code, it can be difficult to find that one line of broken code. With Airbrake Error and Performance Monitoring, you can skip the logs and go straight to the line of broken code resulting in the JSON Parse error.
Don’t have Airbrake? Create your free Airbrake dev account today!
Note: We published this post in February 2017 and recently updated it in April 2022.
The try...catch
statement is comprised of a try
block and either a catch
block, a finally
block, or both. The code in the try
block is executed first, and if it throws an exception, the code in the catch
block will be executed. The code in the finally
block will always be executed before control flow exits the entire construct.
Try it
Syntax
try {
tryStatements
} catch (exceptionVar) {
catchStatements
} finally {
finallyStatements
}
tryStatements
-
The statements to be executed.
catchStatements
-
Statement that is executed if an exception is thrown in the
try
-block. exceptionVar
Optional-
An optional identifier to hold the caught exception for the associated
catch
block. If thecatch
block does not utilize the exception’s value, you can omit theexceptionVar
and its surrounding parentheses, ascatch {...}
. finallyStatements
-
Statements that are executed before control flow exits the
try...catch...finally
construct. These statements execute regardless of whether an exception was thrown or caught.
Description
The try
statement always starts with a try
block. Then, a catch
block or a finally
block must be present. It’s also possible to have both catch
and finally
blocks. This gives us three forms for the try
statement:
try...catch
try...finally
try...catch...finally
Unlike other constructs such as if
or for
, the try
, catch
, and finally
blocks must be blocks, instead of single statements.
try doSomething(); // SyntaxError
catch (e) console.log(e);
A catch
-block contains statements that specify what to do if an exception
is thrown in the try
-block. If any statement within the
try
-block (or in a function called from within the try
-block)
throws an exception, control is immediately shifted to the catch
-block. If
no exception is thrown in the try
-block, the catch
-block is
skipped.
The finally
block will always execute before control flow exits the try...catch...finally
construct. It always executes, regardless of whether an exception was thrown or caught.
You can nest one or more try
statements. If an inner try
statement does not have a catch
-block, the enclosing try
statement’s catch
-block is used instead.
You can also use the try
statement to handle JavaScript exceptions. See
the JavaScript Guide for more information
on JavaScript exceptions.
Unconditional catch-block
When a catch
-block is used, the catch
-block is executed when
any exception is thrown from within the try
-block. For example, when the
exception occurs in the following code, control transfers to the
catch
-block.
try {
throw "myException"; // generates an exception
} catch (e) {
// statements to handle any exceptions
logMyErrors(e); // pass exception object to error handler
}
The catch
-block specifies an identifier (e
in the example
above) that holds the value of the exception; this value is only available in the
scope of the catch
-block.
Conditional catch-blocks
You can create «Conditional catch
-blocks» by combining
try...catch
blocks with if...else if...else
structures, like
this:
try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}
A common use case for this is to only catch (and silence) a small subset of expected
errors, and then re-throw the error in other cases:
try {
myRoutine();
} catch (e) {
if (e instanceof RangeError) {
// statements to handle this very common expected error
} else {
throw e; // re-throw the error unchanged
}
}
The exception identifier
When an exception is thrown in the try
-block,
exception_var
(i.e., the e
in catch (e)
)
holds the exception value. You can use this identifier to get information about the
exception that was thrown. This identifier is only available in the
catch
-block’s scope. If you don’t need the
exception value, it could be omitted.
function isValidJSON(text) {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}
The finally-block
The finally
block contains statements to execute after the try
block and catch
block(s) execute, but before the statements following the try...catch...finally
block. Control flow will always enter the finally
block, which can proceed in one of the following ways:
- Immediately before the
try
block finishes execution normally (and no exceptions were thrown); - Immediately before the
catch
block finishes execution normally; - Immediately before a control-flow statement (
return
,throw
,break
,continue
) is executed in thetry
block orcatch
block.
If an exception is thrown from the try
block, even when there’s no catch
block to handle the exception, the finally
block still executes, in which case the exception is still thrown immediately after the finally
block finishes executing.
The following example shows one use case for the finally
-block. The code
opens a file and then executes statements that use the file; the
finally
-block makes sure the file always closes after it is used even if an
exception was thrown.
openMyFile();
try {
// tie up a resource
writeMyFile(theData);
} finally {
closeMyFile(); // always close the resource
}
Control flow statements (return
, throw
, break
, continue
) in the finally
block will «mask» any completion value of the try
block or catch
block. In this example, the try
block tries to return 1, but before returning, the control flow is yielded to the finally
block first, so the finally
block’s return value is returned instead.
function doIt() {
try {
return 1;
} finally {
return 2;
}
}
doIt(); // returns 2
It is generally a bad idea to have control flow statements in the finally
block. Only use it for cleanup code.
Examples
Nested try-blocks
First, let’s see what happens with this:
try {
try {
throw new Error("oops");
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "finally"
// "outer" "oops"
Now, if we already caught the exception in the inner try
-block by adding a
catch
-block:
try {
try {
throw new Error("oops");
} catch (ex) {
console.error("inner", ex.message);
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "inner" "oops"
// "finally"
And now, let’s rethrow the error.
try {
try {
throw new Error("oops");
} catch (ex) {
console.error("inner", ex.message);
throw ex;
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "inner" "oops"
// "finally"
// "outer" "oops"
Any given exception will be caught only once by the nearest enclosing
catch
-block unless it is rethrown. Of course, any new exceptions raised in
the «inner» block (because the code in catch
-block may do something that
throws), will be caught by the «outer» block.
Returning from a finally-block
If the finally
-block returns a value, this value becomes the return value
of the entire try-catch-finally
statement, regardless of any
return
statements in the try
and catch
-blocks.
This includes exceptions thrown inside of the catch
-block:
(() => {
try {
try {
throw new Error("oops");
} catch (ex) {
console.error("inner", ex.message);
throw ex;
} finally {
console.log("finally");
return;
}
} catch (ex) {
console.error("outer", ex.message);
}
})();
// Logs:
// "inner" "oops"
// "finally"
The outer «oops» is not thrown because of the return in the finally
-block.
The same would apply to any value returned from the catch
-block.
Specifications
Specification |
---|
ECMAScript Language Specification # sec-try-statement |
Browser compatibility
BCD tables only load in the browser