Як я можу надрукувати кругову структуру у форматі JSON?


680

У мене є великий об'єкт, який я хочу перетворити на JSON і надіслати. Однак він має кругову структуру. Я хочу підкинути будь-які кругові посилання, що надходять, і надіслати все, що можна накреслити. Як це зробити?

Дякую.

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

Я хочу впорядкувати obj на:

{"a":"foo"}

5
Чи можете ви опублікувати зразок об’єкта із круговою посиланням, який ви хочете розібрати?
TWickz

3
що - щось на зразок цього ?
Елвін Вонг


2
Пізно на вечірку, але є проект github для вирішення цього питання.
Престон S

тісно пов'язаний з цим питання: stackoverflow.com/questions/23117470 / ...
mathheadinclouds

Відповіді:


606

Використовуйте 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
@Harry Що таке помилка? Я з радістю виправлю відповідь, якщо в ній є якісь неточності.
Роб Ш

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

7
це неправильно, оскільки воно пропустить другий вигляд об'єктів, які містяться двічі, навіть якщо вони не мають дійсно циклічної структури. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "Замінник у цьому прикладі не є на 100% правильним (залежно від вашого визначення" дубліката "). Але концепція стоїть: Використовуйте спеціальний замінник і слідкуйте за проаналізованими значеннями об'єкта."
Роб Ш

4
Занепокоєння GC тут, мабуть, зайве. Якщо це запускається як один сценарій, сценарій негайно припиняється. Якщо це інкапсульоване всередині функції для реалізації, cacheце буде недоступним developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz

704

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



Будь ласка, читайте та дайте коментарі коментаторам нижче ...


134
util - це вбудований модуль, не потрібно його встановлювати.
Мітар

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

Побачені значення повинні зберігатися в наборі , а не в масиві (замінювач отримує виклик на кожному елементі ), і не потрібно намагатися JSON.stringify кожен елемент ланцюга, що веде до кругової посилання.

Як і у прийнятій відповіді, це рішення видаляє всі повторювані значення , а не лише кругові. Але принаймні це не має експоненціальної складності.


Акуратно, але це лише ES2015. Ніякої підтримки IE.
Мартін Каподічі

43
Йода каже: "Якщо 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в хромі
roberto tomás

1
Він працює в React + Typescript. спасибі
користувач3417479

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 працює лише в технічному обслуговуванні, а його наступником є уплощений .


Дуже дякую! Прекрасна бібліотека, заощаджена багато часу. Супер крихітний (просто 1,4 Кб мінімізований).
Брайан Хаак

16
Я думаю, що вам може знадобитися трохи більше обґрунтування для використання модуля, ніж «просто робити». І JSONпринципово не перезаписувати .
Едвін

Мені потрібно було скопіювати об’єкт, який потрібно використовувати для тестування заглушок. Ця відповідь була ідеальною. Я скопіював об’єкт, а потім вилучив перезапис. Дякую!!
Кріс Шарп

1
За словами автора, цей пакет застарілий. CircularJSON працює лише в обслуговуванні, його наступником є ​​уплощений. Посилання: github.com/WebReflection/flatted#flatted
Роберт Моліна

3
Обережно, пакет "сплющений" (і круговий json?) Пакет не копіює функцію JSON.stringify (). Він створює власний не-JSON формат. (напр., Flatted.stringify({blah: 1})результати [{"blah":1}]) Я бачу, що хтось намагався порушити проблему з цього приводу, і автор зривав їх і заблокував це питання до коментарів.
jameslol

48

Мені дуже сподобалось рішення Триндаза - більш багатослівне, проте в ньому були деякі помилки. Я зафіксував їх для тих, хто це теж любить.

Крім того, я додав обмеження довжини для своїх об'єктів кешу.

Якщо об’єкт, який я друкую, дійсно великий - я маю на увазі нескінченно великий - я хочу обмежити свій алгоритм.

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)) + "with key" + printObjectKeys [printObjIndex] + ")";
Ісак

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

2
// браузери не надрукують більше 20K - але ви ставите ліміт як 2k. Можливо, зміниться на майбутнє?
Почен

38

@ Відповідь RobW правильна, але це більш ефективно! Оскільки він використовує хешмап / 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"}}}і навіть видалення вузлів із порожнім об'єктом {}
Сандіп Пінгл

Чи можете ви показати приклад цього Сандіпа? створити 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)));

Я зробив роздрібнення для вирішення цієї проблеми (див. Свою форку cycl.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/isissue
Nux

Це те, що я шукав. JSON.decycle(a, true)що відбувається, коли ви передаєте true як параметр для деципування функції.
Рудра

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

Є пакет npm npmjs.com/package/json-js , але він деякий час не оновлювався
Michael Freidgeim

23

Я рекомендую перевірити 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]'
}

Зауважте, що так само, як і з функцією JSON.stringify ванілі, як згадується @Rob W, ви також можете налаштувати поведінку санітарії, передавши функцію "замінник" як другий аргумент stringify(). Якщо ви виявили, що потребують простий приклад того , як зробити це, я просто написав користувальницький замінник , які примушують помилки, регексп і функцію в рядки людини зчитує тут .


13

Для майбутніх googlers, які шукають рішення цієї проблеми, коли ви не знаєте ключів усіх кругових посилань, ви можете використовувати обгортку навколо функції 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щоб ви (ну, триндаз) не змішували метафори так дивно.
ruffin

@ruffin приємний улов. так, очевидно, завжди використовуйте жорстку рівність і jshint, щоб ловити такі дурні помилки.
guy 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, щоб визначити, чи властивість має тип 'object' (посилання) та точну перевірку рівності (===) для перевірки кругової посилання.


4
Це може працювати лише в IE (враховуючи той факт, що MSDN - це документація від Microsoft, а Microsoft створює IE). У Firefox / Chrome jsfiddle.net/ppmaW генерує помилку кругового посилання. FYI: var obj = {foo:obj}це НЕ створює циклічну посилання. Натомість він створює об’єкт, fooатрибут якого посилається на попереднє значення obj( undefinedякщо раніше не було визначено, оголошено через var obj).
Роб Ш

4

Якщо

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

результати в

TypeError: значення циклічного об'єкта

Тоді ви можете роздрукувати так:

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

21
Може тому, що він друкує лише один рівень?
Алекс Турпін

ДУЖЕ ПРОСТО я підтримував це, тому що він працював на мене прямо з коробки в хромі. ЧУДО
Любов і спокій - Джо Кодесвелл

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-circular , який працює інакше, ніж інші запропоновані способи. Це особливо корисно, якщо ви використовуєте великі та глибокі предмети .

Деякі особливості:

  • Заміна кругових посилань або просто повторених структур всередині об'єкта шляхом, що веде до його першого появи (не лише рядка) [кругового] );

  • Шукаючи кругообіг у першому пошуку за шириною, пакет забезпечує цей шлях якомога меншим, що важливо під час роботи з дуже великими та глибокими об'єктами, де шляхи можуть бути прикро довгими і важкими для дотримання (користувацька заміна в 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"}

Примітка. Як не дивно, що визначення об'єкта з ОП не призводить до кругової помилки посилання в останніх Chrome або Firefox. Визначення в цій відповіді було змінено так , що він зробив видасть помилку.



На цю слід прийняти відповідь
Маніакальна депресія

2

Щоб оновити відповідь на переосмислення способу роботи JSON (мабуть, не рекомендується, але надто просто), не використовуйте circular-json(це застаріло). Натомість використовуйте наступника, укладений:

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 (<анонімний>) в Object. <anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) в JSON.stringify (<анонімний>) в Object. stringifyRecursion [як stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Марк Еллюл

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

Також отримуючи "toISOString - це не функція", намагаючись впорядкувати подію та повторно надіслати її в тест на кипариси
Devin G Rhode,

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

На основі інших відповідей я закінчую наступним кодом. Він досить добре працює з круговими посиланнями, об'єктами з індивідуальними конструкторами.

З даного об'єкта, який підлягає серіалізації,

  • Кешуйте всі об'єкти, на які ви стикаєтесь, об’їжджаючи об’єкт, і присвоюйте кожному з них унікальний хеш-код (також працює автоматичне збільшення номера)
  • Після того, як буде знайдено циркулярну посилання, позначте це поле в новому об'єкті як кругове і збережіть хеш-код початкового об'єкта як атрибут.

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]);}
Клесун

Відповіді, що стосуються лише коду, не враховують. Клацніть по редагуванню та додайте кілька слів, узагальнюючи те, як ваш код вирішує питання, або, можливо, поясніть, чим ваша відповідь відрізняється від попередньої відповіді / відповіді. З огляду
Нік

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.