ejemplos - javascript html
Detección y corrección de referencias circulares en JavaScript. (11)
CircularReferenceDetector
Aquí está mi clase CircularReferenceDetector que genera toda la información de la pila de propiedades donde el valor de referencia circular se encuentra realmente en y también muestra dónde están las referencias responsables.
Esto es especialmente útil para estructuras enormes donde no es obvio por la clave cuyo valor es la fuente del daño.
Produce el valor de referencia circular de forma estriada, pero todas las referencias a sí mismo se reemplazan por "[Objeto circular --- corríjame]".
Uso:
CircularReferenceDetector.detectCircularReferences(value);
Nota: elimine las declaraciones del registrador. * Si no desea utilizar ningún registro o no tiene un registrador disponible.
Explicación técnica:
La función recursiva recorre todas las propiedades del objeto y prueba si JSON.stringify tiene éxito o no. Si no tiene éxito (referencia circular), entonces prueba si tiene éxito reemplazando el valor mismo con una cadena constante. Esto significaría que si tiene éxito utilizando este sustituto, este valor es el valor de referencia circular. Si no lo es, recursivamente recorre todas las propiedades de ese objeto.
Mientras tanto, también realiza un seguimiento de la pila de propiedades para brindarle información sobre dónde se encuentra el valor del culpable.
Mecanografiado
import {Logger} from "../Logger";
export class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:/nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object/n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:/n${circularExcludingStringifyResult}/n`);
}
}
});
}
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
var serializedObjectCounter = 0;
return function (key: any, value: any) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === ''object'' && toBeStringifiedValue === value) {
Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return ''[Circular object --- fix me]'';
}
serializedObjectCounter++;
return value;
}
}
}
export class Util {
static joinStrings(arr: string[], separator: string = ":") {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
JavaScript compilado de TypeScript
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:/nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object/n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:/n${circularExcludingStringifyResult}/n`);
}
}
});
}
static replaceRootStringifyReplacer(toBeStringifiedValue) {
var serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === ''object'' && toBeStringifiedValue === value) {
Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return ''[Circular object --- fix me]'';
}
serializedObjectCounter++;
return value;
};
}
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
static joinStrings(arr, separator = ":") {
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
exports.Util = Util;
Dado que tengo una referencia circular en un objeto grande de JavaScript
Y trato de JSON.stringify(problematicObject)
Y el navegador lanza.
"TypeError: conversión de estructura circular a JSON"
(que se espera)
¿Entonces quiero encontrar la causa de esta referencia circular, preferiblemente utilizando las herramientas de desarrollo de Chrome? es posible? ¿Cómo encontrar y corregir referencias circulares en un objeto grande?
¡La respuesta de @ tmack es definitivamente lo que estaba buscando cuando encontré esta pregunta!
Desafortunadamente, devuelve muchos falsos positivos: devuelve verdadero si un objeto se replica en el JSON, que no es lo mismo que la circularidad. Circularidad significa que un objeto es su propio hijo, por ejemplo,
obj.key1.key2.[...].keyX === obj
Modifiqué la respuesta original, y esto está funcionando para mí:
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (obj && typeof obj != ''object'') { return; }
if (stackSet.has(obj)) { // it''s cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join(''.'') + ''.'' + key;
var l2 = keys.slice(0, oldindex + 1).join(''.'');
console.log(''CIRCULAR: '' + l1 + '' = '' + l2 + '' = '' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object''s children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, ''obj'');
return detected;
}
Aquí hay algunas pruebas muy simples:
var root = {}
var leaf = {''isleaf'':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
Acabo de hacer esto. Puede estar sucio, pero funciona de todos modos ...: P
function dump(orig){
var inspectedObjects = [];
console.log(''== DUMP =='');
(function _dump(o,t){
console.log(t+'' Type ''+(typeof o));
for(var i in o){
if(o[i] === orig){
console.log(t+'' ''+i+'': [recursive]'');
continue;
}
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+'' ''+i+'': [already inspected (''+ind+'')]'');
else{
console.log(t+'' ''+i+'': (''+inspectedObjects.push(o[i])+'')'');
_dump(o[i],t+''>>'');
}
}
}(orig,''>''));
}
Entonces
var a = [1,2,3], b = [a,4,5,6], c = {''x'':a,''y'':b};
a.push(c); dump(c);
Dice
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
Esto indica que cx [3] es igual a c, y cx = cy [0].
O, una pequeña edición de esta función puede indicarle lo que necesita ...
function findRecursive(orig){
var inspectedObjects = [];
(function _find(o,s){
for(var i in o){
if(o[i] === orig){
console.log(''Found: obj.''+s.join(''.'')+''.''+i);
return;
}
if(inspectedObjects.indexOf(o[i])>=0) continue;
else{
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
}
}
}(orig,[]));
}
Aquí está la respuesta de @ Thomas adaptada para el nodo:
const {logger} = require("../logger")
// Or: const logger = {debug: (...args) => console.log.call(console.log, args) }
const joinStrings = (arr, separator) => {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
exports.CircularReferenceDetector = class CircularReferenceDetector {
detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
let value = toBeStringifiedValue[key];
let serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
let isCircularValue;
let circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`);
this.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:/nCircularly referenced value is value under path "${joinStrings(serializationKeyStackWithNewKey)}" of the given root object/n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:/n${circularExcludingStringifyResult}/n`);
}
}
});
}
replaceRootStringifyReplacer(toBeStringifiedValue) {
let serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === ''object'' && toBeStringifiedValue === value) {
logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return ''[Circular object --- fix me]'';
}
serializedObjectCounter++;
return value;
}
}
}
Aquí hay una versión de Node ES6 combinada de las respuestas de @Aaron V y @user4976005 , que corrige el problema con la llamada a hasOwnProperty:
const isCyclic = (obj => {
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) => {
if (!(object instanceof Object))
return
if (stackSet.has(object)) { // it''s cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `${keys.join(''.'')}.${key}`
const l2 = keys.slice(0, oldindex + 1).join(''.'')
console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
console.log(object)
detected = true
return
}
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => { // dive on the object''s children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
})
keys.pop()
stack.pop()
stackSet.delete(object)
})
detect(obj, ''obj'')
return detected
})
Convertí la respuesta de Freddie Nfbnm a TypeScript:
export class JsonUtil {
static isCyclic(json) {
const keys = [];
const stack = [];
const stackSet = new Set();
let detected = false;
function detect(obj, key) {
if (typeof obj !== ''object'') {
return;
}
if (stackSet.has(obj)) { // it''s cyclic! Print the object and its locations.
const oldIndex = stack.indexOf(obj);
const l1 = keys.join(''.'') + ''.'' + key;
const l2 = keys.slice(0, oldIndex + 1).join(''.'');
console.log(''CIRCULAR: '' + l1 + '' = '' + l2 + '' = '' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (const k in obj) { // dive on the object''s children
if (obj.hasOwnProperty(k)) {
detect(obj[k], k);
}
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(json, ''obj'');
return detected;
}
}
Esta es una solución para las respuestas de @Trey Mack y @Freddie Nfbnm en la condición typeof obj != ''object''
Object typeof obj != ''object''
. En su lugar, debe probar si el valor obj
no es una instancia de objeto, de modo que también pueda funcionar cuando se verifican valores con familiaridad con el objeto (por ejemplo, funciones y símbolos (los símbolos no son instancia de objeto, pero aún están direccionados, por cierto)) .
Estoy publicando esto como una respuesta ya que todavía no puedo comentar en esta cuenta de StackExchange.
PS .: no dude en solicitarme que elimine esta respuesta.
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (!(obj instanceof Object)) { return; } // Now works with other
// kinds of object.
if (stackSet.has(obj)) { // it''s cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join(''.'') + ''.'' + key;
var l2 = keys.slice(0, oldindex + 1).join(''.'');
console.log(''CIRCULAR: '' + l1 + '' = '' + l2 + '' = '' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object''s children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, ''obj'');
return detected;
}
Intente usar console.log()
en el navegador chrome / firefox para identificar dónde se produjo el problema.
En Firefox usando el complemento Firebug, puedes depurar tu javascript línea por línea.
Actualizar:
Consulte el siguiente ejemplo de problema de referencia circular y que se ha manejado:
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
if (typeof value === ''object'' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
}
alert("value = ''" + value + "''");
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection
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);
var obj = {
a: "foo",
b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
Consulte el ejemplo de DEMO EN VIVO
Obtenido de http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html . Una línea agregada para detectar dónde está el ciclo. Pega esto en las herramientas de desarrollo de Chrome:
function isCyclic (obj) {
var seenObjects = [];
function detect (obj) {
if (obj && typeof obj === ''object'') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
console.log(obj, ''cycle at '' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
Aquí está la prueba:
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
Si está buscando registrar el objeto, entonces Circular-JSON https://github.com/WebReflection/circular-json podría funcionar para usted.
También puedes usar JSON.stringify
con try / catch
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Manifestación
function hasCircularDependency(obj) {
try {
JSON.stringify(obj);
} catch (e) {
return String(e).includes("Converting circular structure to JSON");
}
return false;
}
var a = {b:{c:{d:""}}};
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));