web-dev-qa-db-ja.com

JSON.stringifyディープオブジェクト

私は任意の引数からJSON有効な文字列を構築する関数が必要です:

  • オブジェクトを2回追加しないことで再帰性の問題を回避する
  • 指定された深さを超えて切り捨てることにより、呼び出しスタックサイズの問題を回避する

一般的に、大きなオブジェクトを切り捨てることを犠牲にして、大きなオブジェクトを処理できるはずです。

参考として、このコードは失敗します:

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

しかし今のところ、コピーと変更を別にして Douglas Crockfordのコード 深さを追跡するために、windoweventのような非常に深いオブジェクトでのスタックオーバーフローを回避する方法を見つけられませんでした。簡単な解決策はありますか?

50
Denys Séguret

私は最初に私がしなければならないことを恐れていたことをしました:私はCrockfordのコードを取り、それを私のニーズに合わせて修正しました。今ではJSONをビルドしますが、処理します

  • サイクル
  • 深すぎるオブジェクト
  • 長すぎる配列
  • 例外(合法的にアクセスできないアクセサー)

誰かがそれを必要とする場合に備えて、GitHubリポジトリを作成しました。 GitHubのJSON.Prune

コードは次のとおりです。

// 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リポジトリ は必要に応じて更新されます(ドキュメント、互換性、モジュールとして使用) commonjsまたはノード、特定のシリアル化など)。このプルーニング機能が必要な場合は、リポジトリから開始することをお勧めします。

90
Denys Séguret

Node.jsを使用している場合は、 util.inspect 、これは深さの引数を取ります。

12

私は@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);
};
5
Gili

次の例のように、単純に Censor 関数を使用できます。

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}

そのため、例として、ウィンドウである値オブジェクトがある場合は、未定義を返す必要があります。

3
Gábor Lipták

循環参照、DOM要素、angularスコープ、またはウィンドウを持つオブジェクトの安全なログのためにJSONを除去するための文字列化機能を次に示します。

防止するTypeError: Converting circular structure to JSON循環参照を ''に置き換えます。

防止するRangeError: Maximum call stack size exceeded。ただし、とにかくmaxDepthまたはfilterObjectsを使用することをお勧めします。非常に深いオブジェクトをシリアル化すると、時間とスペースの両方が消費され、一般的なログ記録の使いやすさが低下し、テストで使用されたときにテストブラウザーが切断されることさえあるためです。

オプションで:

  • オブジェクト検査の深さを制限します(まだ実装されていません)。
  • オブジェクト(ウィンドウ、テストフレームワーク、テストランナーなど)をフィルタリングします。
  • dOM要素をフィルタリングし、
  • filters angular object $ attributes。

ソース+コメント: https://Gist.github.com/iki/937137

0
iki

深さも制限しながら、組み込みの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 ? {} : 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 ? {} : o);
    }
    return JSON.stringify(_build(val, depth), null, space);
}

注:配列は文字列のように扱われます-プリミティブ値の配列。したがって、ネストされたオブジェクト項目は、配列オブジェクト自体ではなく、次のレベルとして扱われます(文字列が文字の配列になりうるが、1つのエンティティであるように)。

0
James Wilkins

あなたが使用しているフォーマットは、あなたが望むことをするのに不適切だと思います。 windowオブジェクトに含まれるすべてのデータを単一のJSON文字列に取得すると、発生した問題の原因となっている彼女を構築している間、この文字列をメモリに保持していると仮定します。

その場でメモリを解放するために、ウィンドウオブジェクトから解析されたデータを送信する機能を提供するフォーマットが必要です。その点については、CSV、Text、またはVarStream( https://github.com/nfroidure/VarStream )のようなものを使用する必要があります。

また、オブジェクト全体を反復処理し、try ... catchでJSON.stringifyを試みることもできます。試行が成功した場合、JSONファイルを送信し、失敗した場合は、同じtry ... catchなどでオブジェクトプロパティを反復処理します。しかし、これはい回避策であり、使用することはお勧めしません。

0
nfroidure