Подтвердить что ты не робот

Как я могу напечатать круговую структуру в JSON-подобном формате?

У меня есть большой объект, который я хочу преобразовать в JSON и отправить. Однако он имеет круговую структуру. Я хочу подбросить любые циркулярные ссылки и отправить все, что может быть сжато. Как это сделать?

Спасибо.

var obj = {
  a: "foo",
  b: obj
}

Я хочу привязать obj к:

{"a":"foo"}
4b9b3361

Ответ 1

Используйте JSON.stringify с пользовательским заменителем. Например:

// Demo: Circular reference
var o = {};
o.o = o;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(o, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // Duplicate reference found, discard key
            return;
        }
        // Store value in our collection
        cache.push(value);
    }
    return value;
});
cache = null; // Enable garbage collection

Заменитель в этом примере не на 100% правильный (в зависимости от вашего определения "дубликат"). В следующем случае значение отбрасывается:

var a = {b:1}
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o, ...);

Но концепция стоит: использовать собственный заменитель и отслеживать проанализированные значения объекта.

Ответ 2

В Node.js вы можете использовать util.inspect (объект). Он автоматически заменяет круглые ссылки на "[Круговой]".


Хотя встроенный (установка не требуется), вы должны импортировать его

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Чтобы использовать его, просто позвоните
console.log(util.inspect(myObject))

Также помните, что вы можете передать параметры объекта для проверки (см. Ссылку выше)

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])



Пожалуйста, прочитайте и отдайте должное комментаторам ниже...

Ответ 3

Интересно, почему никто не выложил правильное решение со страницы MDN...

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

JSON.stringify(circularReference, getCircularReplacer());

Найденные значения должны храниться в наборе, а не в массиве (JSON.stringify каждого элемента), и нет необходимости пытаться JSON.stringify каждого элемента в цепочке, ведущей к циклической ссылке.

Как и в принятом ответе, это решение удаляет все повторяющиеся значения, а не только круглые. Но по крайней мере это не имеет экспоненциальной сложности.

Ответ 4

просто

npm i --save circular-json

то в вашем файле js

const JSON = require('circular-json');
...
const json = JSON.stringify(obj);

Вы также можете сделать

const CircularJSON = require('circular-json');

https://github.com/WebReflection/circular-json

ПРИМЕЧАНИЕ. Я не имею никакого отношения к этому пакету. Но я использую его для этого.

Ответ 5

Мне очень понравилось решение Trindaz - более многословное, однако у него были некоторые ошибки. Я исправил их для тех, кому это тоже нравится.

Плюс, я добавил ограничение длины для своих объектов кеша.

Если объект, который я печатаю, действительно большой - я имею в виду бесконечно большой - я хочу ограничить свой алгоритм.

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
        return 'object too long';
        }
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};

Ответ 6

@RobW ответ правильный, но это более производительный! Потому что он использует hashmap/set:

const customStringify = function (v) {
  const cache = new Set();
  return JSON.stringify(v, function (key, value) {
    if (typeof value === 'object' && value !== null) {
      if (cache.has(value)) {
        // Circular reference found
        try {
          // If this value does not reference a parent it can be deduped
         return JSON.parse(JSON.stringify(value));
        }
        catch (err) {
          // discard key if value cannot be deduped
         return;
        }
      }
      // Store value in our set
      cache.add(value);
    }
    return value;
  });
};

Ответ 7

Обратите внимание, что есть также метод JSON.decycle реализованный Дугласом Крокфордом. Смотрите его cycle.js. Это позволяет структурировать практически любую стандартную структуру:

var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.

Вы также можете воссоздать оригинальный объект методом retrocycle. Таким образом, вам не нужно удалять циклы из объектов, чтобы их упорядочить.

Однако это не будет работать для узлов DOM (которые являются типичной причиной циклов в реальных случаях использования). Например это бросит:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));

Я сделал ответвление, чтобы решить эту проблему (см. Мой fork.js). Это должно работать нормально:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));

Обратите внимание, что в моем форке JSON.decycle(variable) работает так же, как и в оригинале, и JSON.decycle(variable) исключение, когда variable содержит узлы/элементы DOM.

Когда вы используете JSON.decycle(variable, true) вы принимаете тот факт, что результат не будет обратимым (ретроцикл не будет повторно создавать узлы DOM). Элементы DOM должны быть в некоторой степени идентифицируемыми. Например, если элемент div имеет идентификатор, он будет заменен строкой "div#id-of-the-element".

Ответ 8

Я бы рекомендовал проверить json-stringify-safe из @isaacs - он использовался в NPM.

BTW- если вы не используете Node.js, вы можете просто скопировать и вставить строки 4-27 из соответствующую часть исходного кода.

Для установки:

$ npm install json-stringify-safe --save

Для использования:

// Require the thing
var stringify = require('json-stringify-safe');

// Take some nasty circular object
var theBigNasty = {
  a: "foo",
  b: theBigNasty
};

// Then clean it up a little bit
var sanitized = JSON.parse(stringify(theBigNasty));

Это дает:

{
  a: 'foo',
  b: '[Circular]'
}

Обратите внимание, что, как и в случае с функцией vanilla JSON.stringify, как указано в @Rob W, вы также можете настроить поведение санитарии, передав функцию "replacer" в качестве второго аргумента в stringify(). Если вам понадобится простой пример того, как это сделать, я просто написал пользовательский заменитель, который принуждает ошибки, регулярные выражения и функции в читаемые человеком строки здесь.

Ответ 9

Для будущих гуглеров, которые ищут решение этой проблемы, когда вы не знаете ключей всех циклических ссылок, вы можете использовать оболочку вокруг функции JSON.stringify, чтобы исключить циклические ссылки. Смотрите пример сценария на https://gist.github.com/4653128.

По сути, решение сводится к тому, чтобы хранить ссылку на ранее напечатанные объекты в массиве и проверять ее в функции замены перед возвратом значения. Он более ограничен, чем исключение циклических ссылок, поскольку он также исключает возможность печати объекта дважды, одним из побочных эффектов которого является избегание циклических ссылок.

Пример оболочки:

function stringifyOnce(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if(printedObjIndex && typeof(value)=="object"){
            return "(see " + value.constructor.name.toLowerCase() + " with key " + printedObjectKeys[printedObjIndex] + ")";
        }else{
            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
}

Ответ 10

Используйте метод JSON.stringify с помощью замены. Прочтите эту документацию для получения дополнительной информации. http://msdn.microsoft.com/en-us/library/cc836459%28v=vs.94%29.aspx

var obj = {
  a: "foo",
  b: obj
}

var replacement = {"b":undefined};

alert(JSON.stringify(obj,replacement));

Выясните способ заполнения массива замещения циклическими ссылками. Вы можете использовать метод typeof, чтобы узнать, имеет ли свойство тип "объект" (ссылка) и точную проверку равенства (===) для проверки циклической ссылки.

Ответ 11

var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));

имеет значение:

"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"

с функцией:

/**
 * Traverses a javascript object, and deletes all circular values
 * @param source object to remove circular references from
 * @param censoredMessage optional: what to put instead of censored values
 * @param censorTheseItems should be kept null, used in recursion
 * @returns {undefined}
 */
function preventCircularJson(source, censoredMessage, censorTheseItems) {
    //init recursive value if this is the first call
    censorTheseItems = censorTheseItems || [source];
    //default if none is specified
    censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";
    //values that have allready apeared will be placed here:
    var recursiveItems = {};
    //initaite a censored clone to return back
    var ret = {};
    //traverse the object:
    for (var key in source) {
        var value = source[key]
        if (typeof value == "object") {
            //re-examine all complex children again later:
            recursiveItems[key] = value;
        } else {
            //simple values copied as is
            ret[key] = value;
        }
    }
    //create list of values to censor:
    var censorChildItems = [];
    for (var key in recursiveItems) {
        var value = source[key];
        //all complex child objects should not apear again in children:
        censorChildItems.push(value);
    }
    //censor all circular values
    for (var key in recursiveItems) {
        var value = source[key];
        var censored = false;
        censorTheseItems.forEach(function (item) {
            if (item === value) {
                censored = true;
            }
        });
        if (censored) {
            //change circular values to this
            value = censoredMessage;
        } else {
            //recursion:
            value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));
        }
        ret[key] = value

    }

    return ret;
}

Ответ 12

Я знаю, что это старый вопрос, но я хотел бы предложить пакет NPM, который я создал, smart-circular, который отличается от других предложенных способов. Это особенно полезно, если вы используете большие и глубокие объекты.

Некоторые функции:

  • Замена круговых ссылок или просто повторяющихся структур внутри объекта по пути, ведущему к его первому вхождению (а не только к строке [кружок]);

  • При поиске круглостей в первом поиске пакет гарантирует, что этот путь как можно меньше, что важно при работе с очень большими и глубокими объектами, где пути могут раздражать долго и трудно следовать (пользовательская замена в JSON.stringify делает DFS);

  • Позволяет персонализированные замены, удобные для упрощения или игнорирования менее важных частей объекта;

  • Наконец, пути записываются точно так, как нужно для доступа к указанному полю, что может помочь вам отлаживать.

Ответ 13

Если

console.log(JSON.stringify(object));

приводит к

TypeError: значение циклического объекта

Затем вы можете печатать следующим образом:

var output = '';
for (property in object) {
  output += property + ': ' + object[property]+'; ';
}
console.log(output);

Ответ 14

Второй аргумент JSON.stringify() также позволяет вам указать массив имен ключей, которые должны быть сохранены для каждого объекта, с которым он сталкивается в ваших данных. Это может не работать для всех случаев использования, но является гораздо более простым решением.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

var obj = {
    a: "foo",
    b: this
}

var json = JSON.stringify(obj, ['a']);
console.log(json);
// {"a":"foo"}

Примечание. Как ни странно, определение объекта из OP не выдает ошибку циклической ссылки в последних версиях Chrome или Firefox. Определение в этом ответе было изменено таким образом, чтобы оно выдавало ошибку.


Ответ 15

Я нашел круговую json-библиотеку на github, и она хорошо работала для моей проблемы.

Некоторые полезные функции, которые я нашел полезными:

  • Поддерживает многоплатформенное использование, но я тестировал его только с node.js.
  • API такой же, поэтому все, что вам нужно сделать, это включить и использовать его как замену JSON.
  • У него есть собственный метод парсинга, поэтому вы можете преобразовать "циклические" сериализованные данные обратно в объект.

Ответ 16

Я разрешаю эту проблему следующим образом:

var util = require('util');

// Our circular object
var obj = {foo: {bar: null}, a:{a:{a:{a:{a:{a:{a:{hi: 'Yo!'}}}}}}}};
obj.foo.bar = obj;

// Generate almost valid JS object definition code (typeof string)
var str = util.inspect(b, {depth: null});

// Fix code to the valid state (in this example it is not required, but my object was huge and complex, and I needed this for my case)
str = str
    .replace(/<Buffer[ \w\.]+>/ig, '"buffer"')
    .replace(/\[Function]/ig, 'function(){}')
    .replace(/\[Circular]/ig, '"Circular"')
    .replace(/\{ \[Function: ([\w]+)]/ig, '{ $1: function $1 () {},')
    .replace(/\[Function: ([\w]+)]/ig, 'function $1(){}')
    .replace(/(\w+): ([\w :]+GMT\+[\w \(\)]+),/ig, '$1: new Date("$2"),')
    .replace(/(\S+): ,/ig, '$1: null,');

// Create function to eval stringifyed code
var foo = new Function('return ' + str + ';');

// And have fun
console.log(JSON.stringify(foo(), null, 4));

Ответ 17

Я знаю, что этот вопрос старый, и у него много замечательных ответов, но я отправляю этот ответ из-за его нового вкуса (es5 +)

Object.defineProperties(JSON, {
  refStringify: {
    value: function(obj) {

      let objMap = new Map();
      let stringified = JSON.stringify(obj,
        function(key, value) {

          // only for objects
          if (typeof value == 'object') {
            // If has the value then return a reference to it
            if (objMap.has(value))
              return objMap.get(value);

            objMap.set(value, `ref${objMap.size + 1}`);
          }
          return value;
        });
      return stringified;
    }
  },
  refParse: {
    value: function(str) {

      let parsed = JSON.parse(str);
      let objMap = _createObjectMap(parsed);
      objMap.forEach((value, key) => _replaceKeyWithObject(value, key));
      return parsed;
    }
  },
});

// *************************** Example
let a = {
  b: 32,
  c: {
    get a() {
        return a;
      },
      get c() {
        return a.c;
      }
  }
};
let stringified = JSON.refStringify(a);
let parsed = JSON.refParse(stringified, 2);
console.log(parsed, JSON.refStringify(parsed));
// *************************** /Example

// *************************** Helper
function _createObjectMap(obj) {

  let objMap = new Map();
  JSON.stringify(obj, (key, value) => {
    if (typeof value == 'object') {
      if (objMap.has(value))
        return objMap.get(value);
      objMap.set(value, `ref${objMap.size + 1}`);

    }
    return value;
  });
  return objMap;
}

function _replaceKeyWithObject(key, obj, replaceWithObject = obj) {

  Object.keys(obj).forEach(k => {

    let val = obj[k];
    if (val == key)
      return (obj[k] = replaceWithObject);
    if (typeof val == 'object' && val != replaceWithObject)
      _replaceKeyWithObject(key, val, replaceWithObject);
  });
}

Ответ 18

Хотя на этот вопрос был дан достаточный ответ, вы также можете явно удалить данное свойство перед строкой, используя оператор delete.

delete obj.b; 
const jsonObject = JSON.stringify(obj);

удалить оператор

это избавит от необходимости создавать или поддерживать сложную логику для удаления циклических ссылок.

Ответ 19

другое решение для решения этой проблемы с этими типами объектов заключается в том, что с использованием этой библиотеки

https://github.com/ericmuyser/stringy

его просто, и вы можете на нескольких простых шагах решить это.

Ответ 20

Основываясь на других ответах, я получаю следующий код. Он хорошо работает с круговыми ссылками, объектами с настраиваемыми конструкторами.

Из заданного объекта, который будет сериализован,

  • Кэш всего объекта, с которым вы сталкиваетесь при обходе объекта, и назначаете каждому из них уникальный hashID (также работает автоматически увеличивающий число)
  • Как только круговая ссылка найдена, отметьте это поле в новом объекте как круглое и сохраните hashID исходного объекта в качестве атрибута.

Github Link - DecycledJSON

DJSHelper = {};
DJSHelper.Cache = [];
DJSHelper.currentHashID = 0;
DJSHelper.ReviveCache = [];

// DOES NOT SERIALIZE FUNCTION
function DJSNode(name, object, isRoot){
    this.name = name;
    // [ATTRIBUTES] contains the primitive fields of the Node
    this.attributes = {};

    // [CHILDREN] contains the Object/Typed fields of the Node
    // All [CHILDREN] must be of type [DJSNode]
    this.children = []; //Array of DJSNodes only

    // If [IS-ROOT] is true reset the Cache and currentHashId
    // before encoding
    isRoot = typeof isRoot === 'undefined'? true:isRoot;
    this.isRoot = isRoot;
    if(isRoot){
        DJSHelper.Cache = [];
        DJSHelper.currentHashID = 0;

        // CACHE THE ROOT
        object.hashID = DJSHelper.currentHashID++;
        DJSHelper.Cache.push(object);
    }

    for(var a in object){
        if(object.hasOwnProperty(a)){
            var val = object[a];

            if (typeof val === 'object') {
                // IF OBJECT OR NULL REF.

                /***************************************************************************/
                // DO NOT REMOVE THE [FALSE] AS THAT WOULD RESET THE [DJSHELPER.CACHE]
                // AND THE RESULT WOULD BE STACK OVERFLOW
                /***************************************************************************/
                if(val !== null) {
                    if (DJSHelper.Cache.indexOf(val) === -1) {
                        // VAL NOT IN CACHE
                        // ADD THE VAL TO CACHE FIRST -> BEFORE DOING RECURSION
                        val.hashID = DJSHelper.currentHashID++;
                        //console.log("Assigned", val.hashID, "to", a);
                        DJSHelper.Cache.push(val);

                        if (!(val instanceof Array)) {
                            // VAL NOT AN [ARRAY]
                            try {
                                this.children.push(new DJSNode(a, val, false));
                            } catch (err) {
                                console.log(err.message, a);
                                throw err;
                            }
                        } else {
                            // VAL IS AN [ARRAY]
                            var node = new DJSNode(a, {
                                array: true,
                                hashID: val.hashID // HashID of array
                            }, false);
                            val.forEach(function (elem, index) {
                                node.children.push(new DJSNode("elem", {val: elem}, false));
                            });
                            this.children.push(node);
                        }
                    } else {
                        // VAL IN CACHE
                        // ADD A CYCLIC NODE WITH HASH-ID
                        this.children.push(new DJSNode(a, {
                            cyclic: true,
                            hashID: val.hashID
                        }, false));
                    }
                }else{
                    // PUT NULL AS AN ATTRIBUTE
                    this.attributes[a] = 'null';
                }
            } else if (typeof val !== 'function') {
                // MUST BE A PRIMITIVE
                // ADD IT AS AN ATTRIBUTE
                this.attributes[a] = val;
            }
        }
    }

    if(isRoot){
        DJSHelper.Cache = null;
    }
    this.constructorName = object.constructor.name;
}
DJSNode.Revive = function (xmlNode, isRoot) {
    // Default value of [isRoot] is True
    isRoot = typeof isRoot === 'undefined'?true: isRoot;
    var root;
    if(isRoot){
        DJSHelper.ReviveCache = []; //Garbage Collect
    }
    if(window[xmlNode.constructorName].toString().indexOf('[native code]') > -1 ) {
        // yep, native in the browser
        if(xmlNode.constructorName == 'Object'){
            root = {};
        }else{
            return null;
        }
    }else {
        eval('root = new ' + xmlNode.constructorName + "()");
    }

    //CACHE ROOT INTO REVIVE-CACHE
    DJSHelper.ReviveCache[xmlNode.attributes.hashID] = root;

    for(var k in xmlNode.attributes){
        // PRIMITIVE OR NULL REF FIELDS
        if(xmlNode.attributes.hasOwnProperty(k)) {
            var a = xmlNode.attributes[k];
            if(a == 'null'){
                root[k] = null;
            }else {
                root[k] = a;
            }
        }
    }

    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.array){
            // ITS AN [ARRAY]
            root[value.name] = [];
            value.children.forEach(function (elem) {
                root[value.name].push(elem.attributes.val);
            });
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }else if(!value.attributes.cyclic){
            // ITS AN [OBJECT]
            root[value.name] = DJSNode.Revive(value, false);
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }
    });

    // [SEPARATE ITERATION] TO MAKE SURE ALL POSSIBLE
    // [CYCLIC] REFERENCES ARE CACHED PROPERLY
    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.cyclic){
            // ITS AND [CYCLIC] REFERENCE
            root[value.name] = DJSHelper.ReviveCache[value.attributes.hashID];
        }
    });

    if(isRoot){
        DJSHelper.ReviveCache = null; //Garbage Collect
    }
    return root;
};

DecycledJSON = {};
DecycledJSON.stringify = function (obj) {
    return JSON.stringify(new DJSNode("root", obj));
};
DecycledJSON.parse = function (json, replacerObject) {
    // use the replacerObject to get the null values
    return DJSNode.Revive(JSON.parse(json));
};
DJS = DecycledJSON;

Пример 1:

var obj = {
    id:201,
    box: {
        owner: null,
        key: 'storm'
    },
    lines:[
        'item1',
        23
    ]
};

console.log(obj); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonObj = DJS.stringify(obj);
console.log(DJS.parse(jsonObj));

Пример 2:

// PERSON OBJECT

function Person() {
    this.name = null;
    this.child = null;
    this.dad = null;
    this.mom = null;
}
var Dad = new Person();
Dad.name = 'John';
var Mom = new Person();
Mom.name = 'Sarah';
var Child = new Person();
Child.name = 'Kiddo';

Dad.child = Mom.child = Child;
Child.dad = Dad;
Child.mom = Mom;

console.log(Child); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonChild = DJS.stringify(Child);
console.log(DJS.parse(jsonChild));

Ответ 21

Попробуй это:

var obj = {
    a: "foo",
    b: obj
};

var circular_replacer = (value) => {
    var seen = [];
    if (value != null && typeof value == "object") {
        if (seen.indexOf(value) >= 0) return;
        seen.push(value);
    }
    return value;
};

obj = circular_replacer(obj);

Ответ 22

Чтобы обновить ответ о переопределении работы JSON (возможно, не рекомендуется, но очень просто), не используйте circular-json (не рекомендуется). Вместо этого используйте преемник, flatted:

https://www.npmjs.com/package/flatted

Заимствовано из старого ответа выше @user1541685, но заменено новым:

npm я --save flatted

тогда в вашем файле JS

const JSON = require('flatted');

...

const json = JSON.stringify(obj);

Вы могли бы также сделать

const CircularJSON = require('flatted');

Ответ 23

function myStringify(obj, maxDeepLevel = 2) {
  if (obj === null) {
    return 'null';
  }
  if (obj === undefined) {
    return 'undefined';
  }
  if (maxDeepLevel < 0 || typeof obj !== 'object') {
    return obj.toString();
  }
  return Object
    .entries(obj)
    .map(x => x[0] + ': ' + myStringify(x[1], maxDeepLevel - 1))
    .join('\r\n');
}