Stringify (преобразовать в JSON) объект JavaScript с циклической ссылкой
У меня есть определение объекта JavaScript, которое содержит циклическую ссылку: у него есть свойство, которое ссылается на родительский объект.
У него также есть функции, которые я не хочу передавать на сервер. Как бы мне сериализовать и десериализовать эти объекты?
Я читал, что лучший способ сделать это - использовать stringify Дугласа Крокфорда. Тем не менее я получаю следующую ошибку в 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 передается обратно?
5 ответов
Ошибкакруговой структуры возникает, когда у вас есть свойство объекта, которое непосредственно является объектом (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). Это может привести к путанице в будущем...
Без библиотеки
Используйте нижеприведенный заменитель для создания json со строковыми ссылками на повторяющиеся / циклические объекты с ссылками
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);
Похоже, что dojo может представлять циклические ссылки в JSON в форме: {"id":"1","me":{"$ref":"1"}}
Вот пример:
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, даже если есть циклические ссылки?
Произошло с этим потоком, потому что мне нужно было записывать сложные объекты на страницу, так как удаленная отладка была невозможна в моей конкретной ситуации. Найден собственный цикл.js Дугласа Крокфорда (создатель JSON), который аннотирует циклические ссылки в виде строк, чтобы их можно было повторно соединить после анализа. Глубокая копия, очищенная от циклов, безопасна для прохождения через JSON.stringify. Наслаждайтесь!
https://github.com/douglascrockford/JSON-js
cycle.js: этот файл содержит две функции, JSON.decycle и JSON.retrocycle, которые позволяют кодировать циклические структуры и теги в JSON, а затем восстанавливать их. Эта возможность не предоставляется ES5. JSONPath используется для представления ссылок.
Я нашел два подходящих модуля для обработки циклических ссылок в JSON.
- CircularJSON https://github.com/WebReflection/circular-json, выходные данные которого можно использовать в качестве входных данных для.parse(). Это также работает в браузерах и Node.js. См. Также: http://webreflection.blogspot.com.au/2013/03/solving-cycles-recursions-and-circulars.html
- Isaacs json-stringify-safe https://github.com/isaacs/json-stringify-safe который может быть более читабельным, но не может использоваться для.parse и доступен только для Node.js
Любой из них должен удовлетворить ваши потребности.
Я использовал следующее, чтобы исключить циклические ссылки:
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));