Строковое преобразование (преобразование в JSON) объекта JavaScript с круговой ссылкой

У меня есть определение объекта JavaScript, которое содержит циклическую ссылку: у него есть свойство, которое ссылается на родительский объект.

В нем также есть функции, которые я не хочу передавать на сервер. Как мне сериализовать и десериализовать эти объекты?

Я читал, что лучший способ сделать это - использовать строку Дугласа Крокфорда. Однако в Chrome появляется следующая ошибка:

TypeError: преобразование круговой структуры в JSON

Код:

function finger(xid, xparent){
    this.id = xid;
    this.xparent;
    //other attributes
}

function arm(xid, xparent){
    this.id = xid;
    this.parent = xparent;
    this.fingers = [];

    //other attributes

    this.moveArm = function() {
        //moveArm function details - not included in this testcase
        alert("moveArm Executed");
    }
}

 function person(xid, xparent, xname){
    this.id = xid;
    this.parent = xparent;
    this.name = xname
    this.arms = []

    this.createArms = function () {
        this.arms[this.arms.length] = new arm(this.id, this);
    }
}

function group(xid, xparent){
    this.id = xid;
    this.parent = xparent;
    this.people = [];
    that = this;

    this.createPerson = function () {
        this.people[this.people.length] = new person(this.people.length, this, "someName");
        //other commands
    }

    this.saveGroup = function () {
        alert(JSON.stringify(that.people));
    }
}

Это тестовый пример, который я создал для этого вопроса. В этом коде есть ошибки, но, по сути, у меня есть объекты внутри объектов, и каждому объекту передается ссылка, чтобы показать, что является родительским объектом при создании объекта. Каждый объект также содержит функции, которые я не хочу преобразовывать в строки. Мне просто нужны такие свойства, как Person.Name.

Как мне сериализовать перед отправкой на сервер и десериализовать его, предполагая, что тот же JSON передается обратно?


person user1012500    schedule 01.05.2012    source источник
comment
Крокфорд попросил бы вас использовать его cycle.js в качестве < a href = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters" rel = "nofollow noreferrer"> _ 1_ в вашем stringify вызове как указывает @MattEvans ниже.   -  person ruffin    schedule 13.05.2020


Ответы (6)


Ошибка Круговой структуры возникает, когда у вас есть свойство объекта, которое является самим объектом напрямую (a -> a) или косвенно (a -> b -> a).

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

JSON.stringify( that.person, function( key, value) {
  if( key == 'parent') { return value.id;}
  else {return value;}
})

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

Вы можете протестировать приведенный выше код следующим образом:

function Person( params) {
  this.id = params['id'];
  this.name = params['name']; 
  this.father = null;
  this.fingers = [];
  // etc.
}

var me = new Person({ id: 1, name: 'Luke'});
var him = new Person( { id:2, name: 'Darth Vader'});
me.father = him; 
JSON.stringify(me); // so far so good

him.father = me; // time travel assumed :-)
JSON.stringify(me); // "TypeError: Converting circular structure to JSON"
// But this should do the job:
JSON.stringify(me, function( key, value) {
  if(key == 'father') { 
    return value.id;
  } else {
    return value;
  };
});

Кстати, я бы выбрал другое имя атрибута для «parent», поскольку это зарезервированное слово на многих языках (и в DOM). Это имеет тенденцию вызывать путаницу в будущем ...

person tocker    schedule 30.09.2012
comment
На что мы должны изменить переменную parent? - person Esqarrouth; 12.01.2019
comment
@Esqarrouth owner обычно используется вместо parent. - person DJDaveMark; 26.02.2019

Похоже, что dojo может представлять собой круговой ссылки в JSON в виде: {"id":"1","me":{"$ref":"1"}}

Вот пример:

http://jsfiddle.net/dumeG/

require(["dojox/json/ref"], function(){
    var me = {
        name:"Kris",
        father:{name:"Bill"},
        mother:{name:"Karen"}
    };
    me.father.wife = me.mother;
    var jsonMe = dojox.json.ref.toJson(me); // serialize me
    alert(jsonMe);
});​

Производит:

{
   "name":"Kris",
   "father":{
     "name":"Bill",
     "wife":{
          "name":"Karen"
      }
   },
   "mother":{
     "$ref":"#father.wife"
   }
}

Примечание. Вы также можете десериализовать эти объекты с циклическими ссылками, используя метод dojox.json.ref.fromJson.

Другие источники:

Как сериализовать узел DOM в JSON, даже если есть циклические ссылки?

JSON.stringify не может представлять циклические ссылки

person Brandon Boone    schedule 01.05.2012
comment
Привет, спасибо за ответ. Я должен был сказать, что использую jquery в качестве своей библиотеки. В то время я не думал, что это актуально. Я обновлю свой пост. - person user1012500; 01.05.2012
comment
@ user1012500 - Dojo отлично работает вместе с jQuery. Я часто включаю другие библиотеки или фреймворки, чтобы компенсировать недостатки в моем основном фреймворке. Вы даже можете извлечь методы toJson и fromJson и создать вокруг них свою собственную оболочку jQuery. Таким образом, вам не нужно будет тянуть за собой весь фреймворк. К сожалению, jQuery не имеет этой функциональности по умолчанию, а JSON.stringify не может обрабатывать эти типы объектов. Таким образом, кроме приведенных выше примеров, вам, возможно, придется самостоятельно кодировать эту функцию. - person Brandon Boone; 01.05.2012
comment
Привет, Брэндон, я не решаюсь добавить еще одну библиотеку для решения проблемы, поскольку она добавляет еще один след на сайт. Однако я попробовал додзё и попытался использовать ваш пример против моего. Однако я столкнулся с проблемой циклической справки (я плохо разбираюсь в додзё, поэтому я только что попытался сделать несколько вещей, но в основном на основе вашего примера): jsfiddle.net/Af3d6/1 - person user1012500; 02.05.2012
comment
JSON не следует путать с литералы объектов JavaScript не могут содержать функции, поскольку это формат обмена данными (например, XML). Итак, чтобы сериализовать в формате JSON, вам нужен соответствующий литерал объекта. Этот пример изменяет ваш пример, чтобы он соответствовал требованиям (хотя это может быть не то, что вы ищете, поскольку вы re больше не работает с экземплярами объектов). Даже когда вы удаляете свои функции toJSON, ссылки на базовые функции-прототипы по-прежнему делают его недействительным. - person Brandon Boone; 02.05.2012
comment
Привет, В конце концов, мне пришлось пойти одному и настроить свои вызовы stringify. Спасибо, в любом случае. - person user1012500; 27.05.2012
comment
@ user1012500 - Привет, нет проблем. Рад, что вы нашли решение. В интересах других вы должны опубликовать свой ответ и отметить его как правильный ответ, чтобы закрыть этот вопрос. - person Brandon Boone; 29.05.2012
comment
Модуль Dojo ref сумел преобразовать / проанализировать мои модели как ветер. В моем сценарии библиотека cycle.js от crockford не увенчалась успехом, потому что я использую newtonsoft json.net для сериализации объектов C #, и он не использует jsonpath (как использует cycle.js). Похоже, старый добрый додзё спас мне день! Спасибо за этот ответ. - person JoaoPauloPaschoal; 27.02.2019

Я нашел два подходящих модуля для обработки циклических ссылок в JSON.

  1. CircularJSON https://github.com/WebReflection/circular-json, выходные данные которого можно использовать в качестве входных. в .parse (). Он также работает в браузерах и Node.js. См. Также: http://webreflection.blogspot.com.au/2013/03/solving-cycles-recursions-and-circulars.html
  2. Isaacs json-stringify-safe https://github.com/isaacs/json-stringify-safe который может быть более читабельным, но не может использоваться для .parse и доступен только для Node.js

Любой из них должен соответствовать вашим потребностям.

person nevf    schedule 22.07.2013

Без библиотеки

Используйте ниже replacer для создания json. со строковыми ссылками (аналогично json-path) для дублирования / циклических ссылок на объекты

let s = JSON.stringify(obj, refReplacer());

function refReplacer() {
  let m = new Map(), v= new Map(), init = null;

  return function(field, value) {
    let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field); 
    let isComplex= value===Object(value)
    
    if (isComplex) m.set(value, p);  
    
    let pp = v.get(value)||'';
    let path = p.replace(/undefined\.\.?/,'');
    let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
    
    !init ? (init=value) : (val===init ? val="#REF:$" : 0);
    if(!pp && isComplex) v.set(value, path);
   
    return val;
  }
}




// ---------------
// TEST
// ---------------

// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2:  a  }, b, a }; // duplicate reference
a.a3 = [1,2,b];                      // circular reference
b.b3 = a;                            // circular reference


let s = JSON.stringify(obj, refReplacer(), 4);

console.log(s);

И следующая функция парсера для регенерации объекта из такого ref-json

function parseRefJSON(json) {
  let objToPath = new Map();
  let pathToObj = new Map();
  let o = JSON.parse(json);
  
  let traverse = (parent, field) => {
    let obj = parent;
    let path = '#REF:$';

    if (field !== undefined) {
      obj = parent[field];
      path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
    }

    objToPath.set(obj, path);
    pathToObj.set(path, obj);
    
    let ref = pathToObj.get(obj);
    if (ref) parent[field] = ref;

    for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
  }
  
  traverse(o);
  return o;
}



// ------------
// TEST
// ------------

let s = `{
    "o1": {
        "o2": {
            "a1": 1,
            "a2": 2,
            "a3": [
                1,
                2,
                {
                    "b1": 3,
                    "b2": "4",
                    "b3": "#REF:$.o1.o2"
                }
            ]
        }
    },
    "b": "#REF:$.o1.o2.a3[2]",
    "a": "#REF:$.o1.o2"
}`;

console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);

console.log(obj);

person Kamil Kiełczewski    schedule 12.05.2020
comment
отличный! просто и полезно. Благодарность - person Hamed Zakery Miab; 24.05.2021

Произошло в этом потоке, потому что мне нужно было записывать сложные объекты на страницу, поскольку удаленная отладка в моей конкретной ситуации была невозможна. Найден собственный файл cycle.js Дугласа Крокфорда (инициатор JSON), который аннотирует циклические ссылки в виде строк, чтобы их можно было повторно подключить после синтаксического анализа. Дециклированная глубокая копия безопасно проходит через JSON.stringify. Наслаждаться!

https://github.com/douglascrockford/JSON-js

cycle.js: этот файл содержит две функции, JSON.decycle и JSON.retrocycle, которые позволяют кодировать циклические структуры и теги в JSON, а затем восстанавливать их. Этой возможности нет в ES5. JSONPath используется для представления ссылок.

person Matt Evans    schedule 30.03.2017

Я использовал следующее, чтобы исключить циклические ссылки:

JS.dropClasses = function(o) {

    for (var p in o) {
        if (o[p] instanceof jQuery || o[p] instanceof HTMLElement) {
            o[p] = null;
        }    
        else if (typeof o[p] == 'object' )
            JS.dropClasses(o[p]);
    }
};

JSON.stringify(JS.dropClasses(e));
person Timmerz    schedule 28.02.2014
comment
Это удалит экземпляры jQuery и HTMLElement, а не циклические ссылки? - person ZachB; 02.06.2016
comment
@ZachB, я думаю, что в его настройке они для него круговые ... но проблема в том, что то, что используется javascript, не означает, что у нас есть jquery или даже элементы HTML. - person moeiscool; 01.05.2017