programing

JSON.string 딥 오브젝트

nicescript 2023. 4. 2. 13:41
반응형

JSON.string 딥 오브젝트

임의의 인수로 유효한 JSON 문자열을 빌드하는 함수가 필요합니다만,

  • 개체를 두 번 추가하지 않음으로써 재귀성 문제 방지
  • 콜 스택사이즈 문제를 회피하기 위해 지정된 깊이를 넘어 잘라내기

일반적으로 큰 오브젝트를 처리할 수 있어야 합니다.단, 큰 오브젝트를 잘라내는 비용이 듭니다.

참고로 이 코드는 실패합니다.

var json = JSON.stringify(window);

재귀성 문제를 회피하는 것은 간단합니다.

var seen = [];
return JSON.stringify(o, function(_, value) {
    if (typeof value === 'object' && value !== null) {
        if (seen.indexOf(value) !== -1) return;
        else seen.push(value);
    }
    return value;
});

그러나 현재 더글라스 크록포드의 코드를 복사하고 변경해 깊이를 추적하는 것 외에는 다음과 같은 매우 깊은 물체에서 스택 오버플로를 피할 수 있는 방법을 찾을 수 없었습니다.window 임의의 「」를 참조해 주세요.event단한한 해결 ?? ???

나는 처음에 해야 한다고 생각했던 일을 했다: 나는 Crockford의 코드를 내 필요에 맞게 수정했다.이제 JSON을 구축하지만

  • 사이클
  • 너무 깊은 물체
  • 어레이가 너무 길다
  • 예외(법적으로 액세스할 수 없는 액세스 장치)

GitHub에 JSON.prune이라는 GitHub 저장소를 만들었습니다.

코드는 다음과 같습니다.

// JSON.pruned : a function to stringify any object without overflow
// example : var json = JSON.pruned({a:'e', c:[1,2,{d:{e:42, f:'deep'}}]})
// two additional optional parameters :
//   - the maximal depth (default : 6)
//   - the maximal length of arrays (default : 50)
// GitHub : https://github.com/Canop/JSON.prune
// This is based on Douglas Crockford's code ( https://github.com/douglascrockford/JSON-js/blob/master/json2.js )
(function () {
    'use strict';

    var DEFAULT_MAX_DEPTH = 6;
    var DEFAULT_ARRAY_MAX_LENGTH = 50;
    var seen; // Same variable used for all stringifications

    Date.prototype.toPrunedJSON = Date.prototype.toJSON;
    String.prototype.toPrunedJSON = String.prototype.toJSON;

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };

    function quote(string) {
        escapable.lastIndex = 0;
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string'
                ? c
                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + string + '"';
    }

    function str(key, holder, depthDecr, arrayMaxLength) {
        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            partial,
            value = holder[key];
        if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {
            value = value.toPrunedJSON(key);
        }

        switch (typeof value) {
        case 'string':
            return quote(value);
        case 'number':
            return isFinite(value) ? String(value) : 'null';
        case 'boolean':
        case 'null':
            return String(value);
        case 'object':
            if (!value) {
                return 'null';
            }
            if (depthDecr<=0 || seen.indexOf(value)!==-1) {
                return '"-pruned-"';
            }
            seen.push(value);
            partial = [];
            if (Object.prototype.toString.apply(value) === '[object Array]') {
                length = Math.min(value.length, arrayMaxLength);
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value, depthDecr-1, arrayMaxLength) || 'null';
                }
                v = partial.length === 0
                    ? '[]'
                    : '[' + partial.join(',') + ']';
                return v;
            }
            for (k in value) {
                if (Object.prototype.hasOwnProperty.call(value, k)) {
                    try {
                        v = str(k, value, depthDecr-1, arrayMaxLength);
                        if (v) partial.push(quote(k) + ':' + v);
                    } catch (e) { 
                        // this try/catch due to some "Accessing selectionEnd on an input element that cannot have a selection." on Chrome
                    }
                }
            }
            v = partial.length === 0
                ? '{}'
                : '{' + partial.join(',') + '}';
            return v;
        }
    }

    JSON.pruned = function (value, depthDecr, arrayMaxLength) {
        seen = [];
        depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
        arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
        return str('', {'': value}, depthDecr, arrayMaxLength);
    };

}());

수행할 수 있는 작업의 예를 다음에 나타냅니다.

var json = JSON.pruned(window);

주의: 이 답변의 코드와 달리 GitHub 저장소는 필요할 때 업데이트됩니다(문서, 호환성, 공통점 또는 노드의 모듈로 사용, 특정 직렬화 등).이 플루닝 기능이 필요한 경우 저장소부터 시작하는 것이 좋습니다.

Node.js를 사용하는 경우 를 사용할 수 있습니다.이것은 depth 인수를 사용합니다.

@dystroy의 답변을 다음과 같이 수정했습니다.

  • 하위 속성의 들여쓰기입니다.
  • 순환 참조가 가리키는 위치를 나타냅니다.
/**
 * Returns the JSON representation of an object.
 *
 * @param {value} object the object
 * @param {number} objectMaxDepth for objects, the maximum number of times to recurse into descendants
 * @param {number} arrayMaxLength for arrays, the maximum number of elements to enumerate
 * @param {string} indent the string to use for indentation
 * @return {string} the JSON representation
 */
var toJSON = function(object, objectMaxDepth, arrayMaxLength, indent)
{
    "use strict";

    /**
     * Escapes control characters, quote characters, backslash characters and quotes the string.
     *
     * @param {string} string the string to quote
     * @returns {String} the quoted string
     */
    function quote(string)
    {
        escapable.lastIndex = 0;
        var escaped;
        if (escapable.test(string))
        {
            escaped = string.replace(escapable, function(a)
            {
                var replacement = replacements[a];
                if (typeof (replacement) === "string")
                    return replacement;
                // Pad the unicode representation with leading zeros, up to 4 characters.
                return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
            });
        }
        else
            escaped = string;
        return "\"" + escaped + "\"";
    }

    /**
     * Returns the String representation of an object.
     * 
     * Based on <a href="https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js">https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js</a>
     *
     * @param {string} path the fully-qualified path of value in the JSON object
     * @param {type} value the value of the property
     * @param {string} cumulativeIndent the indentation to apply at this level
     * @param {number} depth the current recursion depth
     * @return {String} the JSON representation of the object, or "null" for values that aren't valid
     * in JSON (e.g. infinite numbers).
     */
    function toString(path, value, cumulativeIndent, depth)
    {
        switch (typeof (value))
        {
            case "string":
                return quote(value);
            case "number":
                {
                    // JSON numbers must be finite
                    if (isFinite(value))
                        return String(value);
                    return "null";
                }
            case "boolean":
                return String(value);
            case "object":
                {
                    if (!value)
                        return "null";
                    var valueIndex = values.indexOf(value);
                    if (valueIndex !== -1)
                        return "Reference => " + paths[valueIndex];
                    values.push(value);
                    paths.push(path);
                    if (depth > objectMaxDepth)
                        return "...";

                    // Make an array to hold the partial results of stringifying this object value.
                    var partial = [];

                    // Is the value an array?
                    var i;
                    if (Object.prototype.toString.apply(value) === "[object Array]")
                    {
                        // The value is an array. Stringify every element
                        var length = Math.min(value.length, arrayMaxLength);

                        // Whether a property has one or multiple values, they should be treated as the same
                        // object depth. As such, we do not increment the object depth when recursing into an
                        // array.
                        for (i = 0; i < length; ++i)
                        {
                            partial[i] = toString(path + "." + i, value[i], cumulativeIndent + indent, depth,
                                arrayMaxLength);
                        }
                        if (i < value.length)
                        {
                            // arrayMaxLength reached
                            partial[i] = "...";
                        }
                        return "\n" + cumulativeIndent + "[" + partial.join(", ") + "\n" + cumulativeIndent +
                            "]";
                    }

                    // Otherwise, iterate through all of the keys in the object.
                    for (var subKey in value)
                    {
                        if (Object.prototype.hasOwnProperty.call(value, subKey))
                        {
                            var subValue;
                            try
                            {
                                subValue = toString(path + "." + subKey, value[subKey], cumulativeIndent + indent,
                                    depth + 1);
                                partial.push(quote(subKey) + ": " + subValue);
                            }
                            catch (e)
                            {
                                // this try/catch due to forbidden accessors on some objects
                                if (e.message)
                                    subKey = e.message;
                                else
                                    subKey = "access denied";
                            }
                        }
                    }
                    var result = "\n" + cumulativeIndent + "{\n";
                    for (i = 0; i < partial.length; ++i)
                        result += cumulativeIndent + indent + partial[i] + ",\n";
                    if (partial.length > 0)
                    {
                        // Remove trailing comma
                        result = result.slice(0, result.length - 2) + "\n";
                    }
                    result += cumulativeIndent + "}";
                    return result;
                }
            default:
                return "null";
        }
    }

    if (indent === undefined)
        indent = "  ";
    if (objectMaxDepth === undefined)
        objectMaxDepth = 0;
    if (arrayMaxLength === undefined)
        arrayMaxLength = 50;
    // Matches characters that must be escaped
    var escapable =
        /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
    // The replacement characters
    var replacements =
        {
            "\b": "\\b",
            "\t": "\\t",
            "\n": "\\n",
            "\f": "\\f",
            "\r": "\\r",
            "\"": "\\\"",
            "\\": "\\\\"
        };
    // A list of all the objects that were seen (used to avoid recursion)
    var values = [];
    // The path of an object in the JSON object, with indexes corresponding to entries in the
    // "values" variable.
    var paths = [];
    return toString("root", object, "", 0);
};

다음은 내장된 JSON.stringify() 규칙을 준수하면서 깊이를 제한하는 함수입니다.이 버전에서는 순환 참조를 null로 하거나 옵션콜백을 사용하여 오브젝트 ID(GUID 등)를 취득함으로써 순환 참조를 처리합니다.

function stringify(val, depth, replacer, space, onGetObjID) {
    depth = isNaN(+depth) ? 1 : depth;
    var recursMap = new WeakMap();
    function _build(val, depth, o, a, r) { // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration)
        return !val || typeof val != 'object' ? val
            : (r = recursMap.has(val), recursMap.set(val,true), a = Array.isArray(val),
               r ? (o=onGetObjID&&onGetObjID(val)||null) : JSON.stringify(val, function(k,v){ if (a || depth > 0) { if (replacer) v=replacer(k,v); if (!k) return (a=Array.isArray(v),val=v); !o && (o=a?[]:{}); o[k] = _build(v, a?depth:depth-1); } }),
               o===void 0 ? (a?[]:{}) : o);
    }
    return JSON.stringify(_build(val, depth), null, space);
}

var o = {id:'SOMEGUID',t:true};
var value={a:[12,2,{y:3,z:{o1:o}}],s:'!',b:{x:1,o2:o,o3:o}};

console.log(stringify(value, 0, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 1, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 2, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 3, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2));
console.log(stringify(value, 4, (k,v)=>{console.log('key:'+k+';val:',v); return v}, 2, (v)=>{return v.id}));

{}

{
  "a": [
    12,
    2,
    {}
  ],
  "s": "!",
  "b": {}
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {}
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": {},
    "o3": null
  }
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {
        "o1": {}
      }
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": null,
    "o3": null
  }
}

{
  "a": [
    12,
    2,
    {
      "y": 3,
      "z": {
        "o1": {
          "id": "SOMEGUID",
          "t": true
        }
      }
    }
  ],
  "s": "!",
  "b": {
    "x": 1,
    "o2": "SOMEGUID",
    "o3": "SOMEGUID"
  }

(여기 https://stackoverflow.com/a/57193068/1236397)에서 제 글을 인용합니다.

다음은 TypeScript 버전입니다.

/** A more powerful version of the built-in JSON.stringify() function that uses the same function to respect the
 * built-in rules while also limiting depth and supporting cyclical references.
 */
export function stringify(val: any, depth: number, replacer: (this: any, key: string, value: any) => any, space?: string | number, onGetObjID?: (val: object) => string): string {
    depth = isNaN(+depth) ? 1 : depth;
    var recursMap = new WeakMap();
    function _build(val: any, depth: number, o?: any, a?: boolean, r?: boolean) {
        return !val || typeof val != 'object' ? val
            : (r = recursMap.has(val),
                recursMap.set(val, true),
                a = Array.isArray(val),
                r ? (o = onGetObjID && onGetObjID(val) || null) : JSON.stringify(val, function (k, v) { if (a || depth > 0) { if (replacer) v = replacer(k, v); if (!k) return (a = Array.isArray(v), val = v); !o && (o = a ? [] : {}); o[k] = _build(v, a ? depth : depth - 1); } }),
                o === void 0 ? (a?[]:{}) : o);
    }
    return JSON.stringify(_build(val, depth), null, space);
}

주의: 어레이는 기본 값의 배열인 문자열과 같이 취급됩니다.따라서 네스트된 오브젝트 항목은 어레이 오브젝트 자체가 아닌 다음 레벨로 취급됩니다(문자 배열이 될 수 있지만 하나의 엔티티인 경우와 비슷합니다).

업데이트: 빈 배열이 빈 개체로 렌더링되는 오류를 수정했습니다.

아래 예시와 같이 간단히 관측 중단 기능을 사용할 수 있습니다.

function censor(key, value) {
  if (typeof(value) == "string") {
    return undefined;
  }
  return value;
}

var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, censor);

출력은{"week":45,"month":7}.

따라서 이 예에서는 값 객체(창)가 있는 경우 정의되지 않은 상태로 반환해야 합니다.

당신이 사용하고 있는 포맷은 당신이 원하는 것을 하기에는 적절하지 않다고 생각합니다.창 객체에 포함된 모든 데이터를 단일 JSON 문자열로 가져오는 경우, 이 문자열을 빌드하는 동안 메모리에 저장하여 문제가 발생했다고 가정합니다.

윈도우 오브젝트에서 데이터를 구문 분석할 때 데이터를 전송할 수 있는 포맷이 있어야 메모리를 바로 확보할 수 있습니다.이 경우 CSV, Text 또는 VarStream(https://github.com/nfroidure/VarStream )을 사용해야 합니다.

throught 객체를 반복하여 JSON.string을 시도... catch로 시도합니다.시도가 성공하면 JSON 파일을 전송하고 실패하면 동일한 시도... catch 등으로 개체 속성을 반복합니다.하지만 이건 추악한 회피책이에요. 사용하지 말라고 권하고 싶진 않아요.

주기 참조, DOM 요소, 각도 스코프 또는 창을 사용하여 개체를 안전하게 로깅하기 위해 제거된 JSON에 대한 문자열입니다.

막다TypeError: Converting circular structure to JSON순환 참조를 ''로 바꿈으로써.

막다RangeError: Maximum call stack size exceeded단, maxDepth 또는 filterObjects를 사용하는 것이 좋습니다.이는 매우 깊은 오브젝트를 시리얼화하는 데 시간과 공간이 소요되기 때문에 일반적인 로깅에 대한 사용 편의성이 저하될 수 있으며 테스트에서 사용할 경우 테스트 브라우저의 연결이 끊어질 수도 있기 때문입니다.

옵션:

  • 오브젝트 검사 깊이를 제한합니다(아직 구현되지 않았습니다).
  • 필터 객체(창, 테스트 프레임워크, 테스트 러너 등),
  • DOM 요소를 필터링 합니다.
  • angular object $attribute를 필터링합니다.

출처+댓글: https://gist.github.com/iki/9371373

(function (input, level) {
    if (!input)
        return input;

    level = level || 4;

    var objectsAlreadySerialized = [input],
        objDepth = [input];

    return JSON.stringify(input, function (key, value) {
        if (key) {
            if (typeof value === 'object') {
                if (objectsAlreadySerialized.indexOf(value) !== -1)
                    return undefined;

                objectsAlreadySerialized.push(value);
            }

            if (objDepth.indexOf(this) === -1)
                objDepth.push(this);
            else while(objDepth[objDepth.length-1] !== this)
                objDepth.pop();

            if (objDepth.length > level)
                return undefined;
        }

        return value;
    });
})(window, 6)

현재 수준을 유지할 수 있습니다.

function stringify(obj, currentDepth, maxDepth) {
  if (currentDepth == maxDepth) return '[Warning: max level reached]'
  var str = '{';
  for (var key in obj) {
    str += key + ': ' + typeof obj == 'object' ?
        stringify(obj[key], currentDepth + 1, maxDepth) :
        obj[key];
  }
  return str + '}'
}

(단순히 예- 이 스니펫은 재귀를 검출하지 않습니다)

언급URL : https://stackoverflow.com/questions/13861254/json-stringify-deep-objects

반응형