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


682

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

Спасибо.

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

Я хочу преобразовать obj в:

{"a":"foo"}

5
Не могли бы вы опубликовать образец объекта с круговой ссылкой, которую вы хотите проанализировать?
TWickz

3
что - то вроде этого ?
Элвин Вонг


2
Поздно к вечеринке, но есть github проект, чтобы справиться с этим.
Престон С

тесно связанный вопрос: stackoverflow.com/questions/23117470/…
mathheadinclouds

Ответы:


607

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

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

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(circ, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    // Duplicate reference found, discard key
    if (cache.includes(value)) 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, ...);

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

Как функция полезности написана в es6:

// safely handles circular references
JSON.safeStringify = (obj, indent = 2) => {
  let cache = [];
  const retVal = JSON.stringify(
    obj,
    (key, value) =>
      typeof value === "object" && value !== null
        ? cache.includes(value)
          ? undefined // Duplicate reference found, discard key
          : cache.push(value) && value // Store value in our collection
        : value,
    indent
  );
  cache = null;
  return retVal;
};

// Example:
console.log('options', JSON.safeStringify(options))

1
@ Гарри, что за ошибка? Я с удовольствием исправлю ответ, если в нем есть неточности.
Роб W

1
@CruzDiablo Сериализация DOM обычно бессмысленна. Однако, если вы можете подумать о значимом методе сериализации для своих целей, то вы можете попытаться добавить настраиваемый сериализованный объект DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(если вы хотите что-то более общее / конкретное, просто попробуйте что-нибудь в дереве прототипа: HTMLDivElement реализует реализации HTMLElement Элемент реализует, Node реализует EventTarget, обратите внимание: это может зависеть от браузера, предыдущее дерево верно для Chrome)
Роб W

7
это неправильно, потому что он пропустит второе появление объектов, которые содержатся дважды, даже если не в действительно циклической структуре. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 «Заменитель в этом примере не на 100% корректен (в зависимости от вашего определения« дубликата »). Но концепция стоит так: используйте настраиваемый заменитель и отслеживайте проанализированные значения объекта.»
Роб W

4
Концерн GC здесь, возможно, избыточен. Если это выполняется как один сценарий, сценарий немедленно завершается. Если это инкапсулировано внутри функции для реализации, тогда cacheбудет недоступен developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz

704

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


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

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}])



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


134
Утилита является встроенным модулем, вам не нужно его устанавливать.
Митар

10
console.log (util.inspect (obj))
starsinmypockets

19
@Mitar это встроенный, но вы все равно должны загрузить модульvar util = require('util');
Bodecker

14
Не будь таким тупицей, как я, это просто obj_str = util.inspect(thing) , НЕ <s> garbage_str = JSON.stringify(util.inspect(thing))</ s>
ThorSummoner

7
Это намного лучше, чем возиться с проверкой типов. Почему нельзя просто так работать? Если он знает, что есть циклическая ссылка, почему нельзя просто сказать, что он игнорируется ???
Крис Пикок

141

Интересно, почему никто не выложил правильное решение со страницы 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());

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

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


Аккуратно, но это только ES2015. Нет поддержки IE.
Мартин

44
Йода говорит: «Если вы все еще поддерживаете IE, используйте транспилер».
Поезд Испании

1
replacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; } () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); … JSON.stringify({a:1, b: '2'}, replacer)возвращается undefinedв Chrome
Роберто Томас

1
Работает в React + Typescript. спасибо
user3417479

76

просто делать

npm i --save circular-json

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

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

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

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

Обновление 2020

Обратите внимание, что CircularJSON находится только в обслуживании, и его наследником является flatted .


Большое спасибо! Отличная библиотека, сэкономила кучу времени. Супер крошечный (всего 1.4KB уменьшен).
Брайан Хаак

16
Я думаю, что вам может потребоваться больше оправданий для использования модуля, чем "просто сделать". И это не здорово переписать JSONв принципе.
Эдвин

Мне нужно было скопировать объект для использования в тестировании заглушки. Этот ответ был идеальным. Я скопировал объект, а затем удалил переопределение. Спасибо!!
Крис Шарп

1
По словам автора, этот пакет устарел. CircularJSON находится только в обслуживании, плоский является его преемником. Ссылка: github.com/WebReflection/flatted#flatted
Роберт Молина

3
Осторожно, пакет 'flatted' (и round-json?) Не дублирует функциональность JSON.stringify (). Он создает свой собственный не-JSON формат. (например, Flatted.stringify({blah: 1})результаты в [{"blah":1}]) Я вижу, что кто-то пытался поднять проблему по этому поводу, и автор ругал их и закрывал проблему для комментариев.
jameslol

48

Мне очень понравилось решение 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);
};

В этой строке пропущена нулевая проверка: return "(см." + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "с ключом" + printObjectKeys [printObjIndex] + ")";
Исак

Я с удовольствием добавлю это. просто дайте мне знать, что обнулять, так как у меня до сих пор были какие-то проблемы.
парень mograbi

2
// браузеры не будут печатать больше 20К - но вы устанавливаете ограничение как 2К. Возможно, изменится на будущее?
Почен

38

Ответ @ 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;
  });
};

Для глубоко вложенных объектов с круговыми ссылками попробуйте stringifyDeep => github.com/ORESoftware/safe-stringify
Александр Миллс,

Возможно, что реализация Set просто использует массив и indexOf под капотом, но я не подтвердил это.
Александр Миллс

Это удаляет родительские узлы, имеющие дочерние узлы, даже с разными значениями - например, - {"a":{"b":{"a":"d"}}}и даже удаляет узлы, имеющие пустой объект {}
Sandip Pingle

Можете ли вы показать пример этого Сандипа? создать gist.github.com или еще много чего
Александр Миллс

Отлично !!! Первое (сверху, но проверено только 2-3 функциональных решения) рабочее решение здесь под node.js и Fission ;-) - зависание библиотек.
Том

37

Обратите внимание, что есть также 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)работает так же, как в оригинале, и выдаст исключение, когдаvariable содержат DOM-узлы / элементы.

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


2
И его код, и ваш код дают мне «RangeError: Превышен максимальный размер стека вызовов», когда я их использую.
jcollum

Я могу посмотреть, если вы предоставите свой код на Fiddle или добавите проблему на Github: github.com/Eccenux/JSON-js/issues
Nux

Это то, что я искал. JSON.decycle(a, true)что происходит, когда вы передаете true в качестве параметра для отмены функции.
Рудра

@Rudra true делает stringifyNodesвариант верным в форке. Это сбросит например , divс идентификатором = «какой - то-идентификатор» в строку: div#some-id. Вы избежите некоторых проблем, но вы не сможете полностью вернуться в цикл.
Nux

Существует пакет npm npmjs.com/package/json-js , но некоторое время он не обновлялся
Майкл

23

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

Кстати, если вы не используете 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]'
}

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


13

Для будущих гуглеров, которые ищут решение этой проблемы, когда вы не знаете ключей всех циклических ссылок, вы можете использовать оболочку вокруг функции 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);
}

3
Хороший код Хотя у вас есть глупая ошибка, вы пишете, if(printedObjIndex)когда должны писать, if(printedObjIndex==false)потому что indexтакже может быть 0переведено, falseесли вы явно не укажете иначе.
парень mograbi

1
@guymograbi Разве ты не имеешь в виду ===? 0 == falseесть true, 0 === falseесть false. ; ^) Но я бы предпочел не инициализировать printedObjIndexкак false, так как тогда вы можете проверить, undefinedчтобы вы (ну, Trindaz) не смешивали метафоры так странно.
ruffin

@ ruffin хороший улов. да, очевидно, всегда используйте жесткое равенство и jshint, чтобы поймать такие глупые ошибки.
парень mograbi

4

Используйте метод 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, чтобы определить, имеет ли свойство тип «объект» (ссылка) и точную проверку на равенство (===) для проверки циклической ссылки.


4
Это может работать только в IE (учитывая тот факт, что MSDN является документацией от Microsoft, а Microsoft создает IE). В Firefox / Chrome jsfiddle.net/ppmaW генерирует ошибку циклической ссылки. FYI: var obj = {foo:obj}это не создает циклическую ссылку. Вместо этого он создает объект, fooатрибут которого ссылается на предыдущее значение obj( undefinedесли не определено ранее, объявлено из-за var obj).
Роб W

4

Если

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

приводит к

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

Тогда вы можете напечатать так:

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

21
Может потому что печатает только один уровень?
Алекс Турпин

ОЧЕНЬ ПРОСТО я проголосовал за это, потому что он работал для меня прямо из коробки в Chrome. ОТЛИЧНО
Любовь и мир - Джо Кодсвелл,

4
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;
}

3

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

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

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

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

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

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


3

Второй аргумент 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. Определение в этом ответе было изменено так , что он сделал выдаст ошибку.



Этот должен быть принят ответ
маниакальная депрессия

2

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

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

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

npm i --save flatted

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

const CircularJSON = require('flatted');
const json = CircularJSON.stringify(obj);

1

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

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

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

2
Эта библиотека вызвала ошибку для меня, поэтому я должен искать другую. ОШИБКА TypeError: toISOString не является функцией в String.toJSON (<anonymous>) в Object. < Anonymous > ( localhost: 8100 / build / polyfills.js: 1: 3458 ) в JSON.stringify (<anonymous>) в Object. stringifyRecursion [as stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Марк

1
@MarkEllul Я написал комментарий в 2015 году, и если я увижу лучшую альтернативу, я опубликую его здесь с изменениями. Я все еще иногда сталкиваюсь с той же проблемой в повседневной работе, и я обычно предпочитаю свои собственные ручные функции рекурсивным способом с надлежащей / безопасной проверкой. Я бы посоветовал ознакомиться с практикой функционального программирования, если вы незнакомы, обычно это упрощает рекурсивные операции такого рода, поскольку они менее сложны и более надежны.
JacopKane

Также получение «toISOString - это не функция», пытающееся упорядочить событие и повторно отправить его в тесте на кипарис
Девин Г. Род

1

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

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));

Это довольно хорошо сработало для меня, но кажется, что классы были представлены примерно _class: ClassName { data: "here" }так, поэтому я добавил следующее правило .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). В моем случае я пытался увидеть, как выглядит объект http-запроса.
Redbmk

1

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


1

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

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

оператор удаления

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


1
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');
}

0

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

https://github.com/ericmuyser/stringy

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


0

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

От данного объекта, подлежащего сериализации,

  • Кэшируйте все объекты, с которыми вы сталкиваетесь при обходе объекта, и присваивайте каждому из них уникальный 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));

0

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

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);

Разве после seen.push(value)= -D не должно быть больше строк кода ? Нравитсяfor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun

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

0

В моем решении, если вы столкнетесь с циклом, он не просто говорит «цикл» (или ничего), он говорит что-то вроде foo: см. Объект № 42 выше, и чтобы увидеть, куда указывает foo, вы можете прокрутить вверх и найти для объекта # 42 (каждый объект, когда он запускается, говорит объект # xxx с некоторым целым числом xxx)

Отрывок:

(function(){
	"use strict";
	var ignore = [Boolean, Date, Number, RegExp, String];
	function primitive(item){
		if (typeof item === 'object'){
			if (item === null) { return true; }
			for (var i=0; i<ignore.length; i++){
				if (item instanceof ignore[i]) { return true; }
			}
			return false;
		} else {
			return true;
		}
	}
	function infant(value){
		return Array.isArray(value) ? [] : {};
	}
	JSON.decycleIntoForest = function decycleIntoForest(object, replacer) {
		if (typeof replacer !== 'function'){
			replacer = function(x){ return x; }
		}
		object = replacer(object);
		if (primitive(object)) return object;
		var objects = [object];
		var forest  = [infant(object)];
		var bucket  = new WeakMap(); // bucket = inverse of objects 
		bucket.set(object, 0);       // i.e., map object to index in array
		function addToBucket(obj){
			var result = objects.length;
			objects.push(obj);
			bucket.set(obj, result);
			return result;
		}
		function isInBucket(obj){
			return bucket.has(obj);
			// objects[bucket.get(obj)] === obj, iff true is returned
		}
		function processNode(source, target){
			Object.keys(source).forEach(function(key){
				var value = replacer(source[key]);
				if (primitive(value)){
					target[key] = {value: value};
				} else {
					var ptr;
					if (isInBucket(value)){
						ptr = bucket.get(value);
					} else {
						ptr = addToBucket(value);
						var newTree = infant(value);
						forest.push(newTree);
						processNode(value, newTree);
					}
					target[key] = {pointer: ptr};
				}
			});
		}
		processNode(object, forest[0]);
		return forest;
	};
})();
the = document.getElementById('the');
function consoleLog(toBeLogged){
  the.textContent = the.textContent + '\n' + toBeLogged;
}
function show(root){
	var cycleFree = JSON.decycleIntoForest(root);
	var shown = cycleFree.map(function(tree, idx){ return false; });
	var indentIncrement = 4;
	function showItem(nodeSlot, indent, label){
	  leadingSpaces = ' '.repeat(indent);
      leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
	  if (shown[nodeSlot]){
	  consoleLog(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
        } else {
		  consoleLog(leadingSpaces + label + ' object#' + nodeSlot);
		  var tree = cycleFree[nodeSlot];
		  shown[nodeSlot] = true;
		  Object.keys(tree).forEach(function(key){
			var entry = tree[key];
			if ('value' in entry){
			  consoleLog(leadingSpacesPlus + key + ": " + entry.value);
                } else {
					if ('pointer' in entry){
						showItem(entry.pointer, indent+indentIncrement, key);
                    }
                }
			});
        }
    }
	showItem(0, 0, 'root');
}
cities4d = {
	Europe:{
		north:[
			{name:"Stockholm", population:1000000, temp:6},
			{name:"Helsinki", population:650000, temp:7.6}
		],
		south:[
			{name:"Madrid", population:3200000, temp:15},
			{name:"Rome", population:4300000, temp:15}
		]
	},
	America:{
		north:[
			{name:"San Francisco", population:900000, temp:14},
			{name:"Quebec", population:530000, temp:4}
		],
		south:[
			{name:"Rio de Janeiro", population:7500000, temp:24},
			{name:"Santiago", population:6300000, temp:14}
		]
	},
	Asia:{
		north:[
			{name:"Moscow", population:13200000, temp:6}
		]
	}
};
cities4d.Europe.north[0].alsoStartsWithS = cities4d.America.north[0];
cities4d.Europe.north[0].smaller = cities4d.Europe.north[1];
cities4d.Europe.south[1].sameLanguage = cities4d.America.south[1];
cities4d.Asia.ptrToRoot = cities4d;
show(cities4d)
<pre id="the"></pre>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.