Многие разработчики не раз за свою практику сталкивались с тем, что стандартная возможность упаковать объект в JSON
падает с ошибкой, или, что хуже, уходит в вечный цикл. Причина тому цикличные ссылки. Пример:
const a = {}; const b = { a }; a.b = b; JSON.stringify(a);
Получаем закономерный Uncaught TypeError: Converting circular structure to JSON
. Но, что если вам, к примеру, для нужд debug
-а, потребовался полунепробиваемый метод toJSON
?
Хорошая новость! Мы можем его написать самостоятельно. Причём, благодаря наличию таких классов, как Map
или Set
, без особых ухищрений и извращений. Принцип действия алгоритма достаточно прост: всякий раз, когда мы собираемся упаковать очередной объект (а только они передаются по ссылке, и приводят к цикличности), мы должны проверить, а не упаковывали ли мы его ранее. А т.к. Set
и Map
в качестве ключа в Map
и значения в Set
понимают всё, что угодно, включая объекты
, не приводя их к строковому виду, то и переложить эту работёнку можно и нужно именно на них.
Circular
Т.е. где-то внутри нашего toJSON
будет:
if(map.has(obj)) return `$circular:${map.get(obj)}`; map.set(obj, path);
Где path
это путь к текущему итерируемому элементу. Таким образом, столкнувшись с объектом повторно, мы, вместо этого объекта, упакуем строку вида $circular:a.b.c
. Что будет очень удобным подспорьем при разборе сложных багов.
Element-ы
Не будет лишним проверять — является ли объект DOMElement
-ом, Document
-ом или чем-нибудь подобным, т.к. эти сущности не просты внутри, но при этом, едва ли представляют для вас интерес:
if( obj instanceof RegExp || obj instanceof Date || obj instanceof Element || obj instanceof Document ) return obj.toString();
Получим строку вида [object HTMLDivElement]
.
jQuery
В случае, если вы используете jQuery
, то стоит все порождённые ею jQuery-массивы
превратить в обычные:
if(obj instanceof $) // jQuery-nodes return _.map(obj, (v, idx) => { /* toJS */ });
В моём случае типовым результатом будет — [“[object HTMLDivElement]”, “[object HTMLDivElement]”]
. Но можно и усложнить схему, конвертируя DOM-ноды
в их CSS-path
(например: #section.cls[attr=“value”]
).
NaN & Null
Скорее всего, внутри своего toJSON вы будете использовать typeof obj === ‘object’
. Напоминаю о засаде — typeof null === ‘object’
; Не забудьте это учесть.
Про NaN — JSON.stringify(NaN) === “null”
. Не самый лучший вариант, не правда ли?
if(_.isNaN(obj)) return '$NaN$';
Дело в том, что JSON
не умеет никаких NaN
. Но для нужд дебага такие моменты могут быть критичными. Тоже самое и для undefined
.