Как проверить, указывает ли свойство на любого из предков [дубликат]

Вот процедура разбиения списка, которую я написал пару месяцев назад:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
44
задан Mike Samuel 19 February 2013 в 18:42
поделиться

11 ответов

Ответ @ tmack определенно то, что я искал, когда нашел этот вопрос!

К сожалению, он возвращает много ложных срабатываний - он возвращает true, если объект реплицируется в JSON, что не совпадает с округлостью. Циркулярность означает, что объектом является его собственный ребенок, например.

obj.key1.key2.[...].keyX === obj

Я изменил исходный ответ, и это работает на меня:

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

Вот несколько очень простых тестов:

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
27
ответ дан Aaron V 20 August 2018 в 18:08
поделиться
  • 1
    Добавление memoization, чтобы сделать это O (N) оставлено в качестве упражнения для читателя :) – Aaron V 20 January 2016 в 20:46
  • 2
    Я добавил версию ответа этого типа в качестве нового ответа. Смотри ниже. – mvermand 28 September 2017 в 05:04
  • 3
    if (typeof obj != 'object') { return; } должен быть if (obj && typeof obj != 'object') { return; }, потому что typeof null == "object". – jcubic 29 September 2017 в 07:55
  • 4
    @Willwsharp - это было какое-то время, но я считаю, что идея заключалась в том, что определенно можно встретить один и тот же объект более одного раза, даже в некруговой структуре. Итак, в худшем случае код, который я написал здесь, обрабатывал одни и те же объекты снова и снова, делая это без необходимости O (N ^ 2). Вы можете использовать memoization для хранения дочерних объектов каждого объекта и предотвращения повторной обработки. – Aaron V 13 June 2018 в 19:12
  • 5
    @AaronV о, потрясающе! Я немного поработал над этим, и я пришел к такому же выводу, настолько рад, что услышал, что я на правильном пути; благодаря! – Willwsharp 14 June 2018 в 13:38

Если вы хотите зарегистрировать объект, то для вас может работать Circular-JSON https://github.com/WebReflection/circular-json .

0
ответ дан Aneesh 20 August 2018 в 18:08
поделиться

Здесь ответ Томаса , адаптированный для узла:

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;
    }
  }
}
0
ответ дан Carl G 20 August 2018 в 18:08
поделиться

Вот версия Node ES6, смешанная с ответами из @Aaron V и @ user4976005 , она устраняет проблему с вызовом 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
})
0
ответ дан dkurzaj 20 August 2018 в 18:08
поделиться

Вы также можете использовать JSON.stringify с try / catch

function hasCircularDependency(obj)
{
    try
    {
        JSON.stringify(obj);
    }
    catch(e)
    {
        return e.includes("Converting circular structure to JSON"); 
    }
    return false;
}

Demo

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

1
ответ дан gurvinder372 20 August 2018 в 18:08
поделиться
  • 1
    )) Это определенно неправильный подход, который я нашел как самое простое решение – Undefitied 24 May 2018 в 08:56

Вытащено из http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html . Одна строка добавлена ​​для определения того, где находится цикл. Вставьте это в инструменты Chrome dev:

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

Вот тест:

> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
  Object {a: Object}
   "cycle at a"
  Object {b: Object}
   "cycle at b"
  true
42
ответ дан JellicleCat 20 August 2018 в 18:08
поделиться
  • 1
    Не забудьте добавить сам объект JSON root к seenObjects. Это должно быть var seenObjects = [obj];. – Aadit M Shah 19 February 2013 в 18:37
  • 2
    @AaditMShah, поскольку на входной объект вызывается detect, что приведет к его немедленному завершению, утверждая, что есть цикл непосредственно из входного объекта для себя. – Mike Samuel 19 February 2013 в 18:44
  • 3
    Это ложные срабатывания на null и не рекурсивно – Andy Ray 24 June 2014 в 06:27
  • 4
    Просто потому, что ссылка используется более одного раза, не обязательно означает, что она циклическая. var x = {}; JSON.stringify([x,x]) в порядке ... пока var x = {}; x.x = x; JSON.stringify(x); нет. – canon 14 May 2015 в 13:58
  • 5
    По какой-то причине, когда я использую это решение, это дает мне ошибку: obj.hasOwnProperty is not a function. Это выполняется внутри скрипта, загруженного браузером. – Ayush Goel 21 March 2018 в 19:11

Я только что сделал это. Это может быть грязно, но все равно работает ...: 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,'>'));
}

Затем

var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);

Говорит

== 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

Это говорит о том, что cx [ 3] равно c, а cx = cy [0].

Или небольшое редактирование этой функции может рассказать вам, что вам нужно ...

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,[]));
}
0
ответ дан JiminP 20 August 2018 в 18:08
поделиться
  • 1
    – Aadit M Shah 19 February 2013 в 18:39
  • 2
    Я намеренно выделил проверку исходного объекта (по if(o[i] === orig)) и другим объектам (по inspectedObjects.indexOf(o[i])). – JiminP 19 February 2013 в 19:02
  • 3
    Просто потому, что ссылка используется более одного раза, не обязательно означает, что она циклическая. var x = {}; JSON.stringify([x,x]) в порядке ... пока var x = {}; x.x = x; JSON.stringify(x); нет. – canon 14 May 2015 в 13:57

Я преобразовал ответ Freddie Nfbnm в 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;
    }

}
0
ответ дан mvermand 20 August 2018 в 18:08
поделиться

Попробуйте использовать console.log() в браузере Chrome / Firefox, чтобы определить, где возникла проблема.

В Firefox с использованием Firebug-плагина вы можете отлаживать ваш javascript по строкам.

Обновление:

См. Пример круговой справки и который был обработан: -

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

См. Пример LIVE DEMO

-1
ответ дан Siva Charan 20 August 2018 в 18:08
поделиться
  • 1
    Вы имеете в виду вручную поиск ссылки? Это, в крайнем случае, лучшее решение. – Šime Vidas 19 February 2013 в 18:12
  • 2
    "вы можете отлаживать строку javascript по строке & quot; - Bulit-in JSON.stringify() не реализована в JavaScript, а в качестве внутреннего кода. Вы не можете отлаживать его с помощью Firebug. – Šime Vidas 19 February 2013 в 18:15
  • 3
    Существует множество библиотек, которые предоставляют функцию stringify () в javascript для старых браузеров. Может быть, вы можете использовать один из них? Это, по-видимому, ломается в точном месте, когда он разбирает проблему. – kbelder 19 February 2013 в 18:26
  • 4
    Просто потому, что ссылка используется более одного раза, не обязательно означает, что она циклическая. var x = {}; JSON.stringify([x,x]) в порядке ... пока var x = {}; x.x = x; JSON.stringify(x); нет. – canon 14 May 2015 в 13:52

CircularReferenceDetector

Вот мой класс CircularReferenceDetector , который выводит всю информацию о стеке свойств, где фактически указано значение с круговым ссылкой, а также показывает, где указаны ссылки виновника.

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

Он выводит значение, связанное с круговым значением, строковое, но все ссылки на себя заменены на «[Circular object --- fix me]».

Использование: CircularReferenceDetector.detectCircularReferences(value);

Примечание. Удалите операторы Logger. *, Если вы не хотите использовать какой-либо журнал или делать

Техническое объяснение: рекурсивная функция проходит через все свойства объекта и проверяет, удастся ли им выполнить JSON.stringify или нет. Если это не удастся (круговая ссылка), тогда он проверяет, удастся ли это путем замены значения самой константной строкой. Это означало бы, что, если ему удастся использовать этот заменитель, это значение является значением, связанным с круговым значением. Если это не так, он рекурсивно проходит через все свойства этого объекта.

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

Тип машинописного текста

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 из 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;

4
ответ дан Thomas 20 August 2018 в 18:08
поделиться

Это исправление для ответов @Trey Mack и @Freddie Nfbnm в typeof obj != 'object'. Вместо этого он должен проверить, является ли значение obj не экземпляром объекта, так что он также может работать при проверке значений с знакомности объекта (например, функции и символы (символы не являются экземпляром объекта, но все еще адресованы, кстати. )).

Я отправляю это как ответ, так как я еще не могу комментировать эту учетную запись StackExchange.

PS .: не стесняйтесь просить меня удалить этот ответ.

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;
}
4
ответ дан user 20 August 2018 в 18:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: