web-dev-qa-db-ja.com

JSONオブジェクトの$ refを解決するにはどうすればよいですか?

JSONサービスを取得するためにRESTサービスを使用する単一ページアプリケーションを作成しました。返されるJSONオブジェクトは、Newtonsoft.JSONライブラリを使用してシリアル化されたC#オブジェクトです。返されたJSONオブジェクトには、同じJSONオブジェクト内の特定の$ idオブジェクトへの$ refポインターが含まれます( ここでの質問は例を示しています )。 $ refが指す実際のデータにアクセスする必要がありますが、元のソースを特定せずにデータにアクセスする方法がわかりません。

新しいコントラクトシリアライザーを記述したり、参照を解決するための独自の実装を記述したりするような解決策を見つけました。この2つよりも簡単な解決策があるはずだと思います。 Newtonsoft JSONシリアル化設定についても調べました。 PreserveReferenceHandlingをNoneに設定してみましたが、成功していません。

私が述べたもの以外の解決策を探しています。実装するコードがほとんどなく、追加のフレームワークが不要なシンプルなソリューションがあることを願っています。

更新

私はこれについてもう少し読んでいて、$ refがJSONポインターのフラグメントであることを発見しました。参照番号は、別のスキーマのアイテムを参照するIDです。それが解決策のように見えますが、それが機能するためにはスキーマを定義する必要があります。

JSONポインターフラグメントのRFC

JSONポインターフラグメントに関する情報

参照:
循環JSON参照を解決
シリアル化設定

5
Cameron McKay

このブロガーが投稿したコードをAngularに準拠するように]変更することで、jsonコードの "$ ref"リンクを解決できました。

http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html

NewtonSofts JSONシリアライザーから取得した「$ ref」を解決するためにangularサービスを作成しました。

// http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html
(function () {
'use strict';

angular
    .module('app')
    .factory('jsonPointerParseService', jsonPointerParseService);

jsonPointerParseService.$inject = [];

function jsonPointerParseService() {
    var hashOfObjects = {};

    var service = {
        pointerParse: pointerParse
    };

    return service;

    function collectIds(obj) {
        if (jQuery.type(obj) === "object") {
            if (obj.hasOwnProperty("$id")) {
                hashOfObjects[obj.$id] = obj;
            }
            for (var prop in obj) {
                collectIds(obj[prop]);
            }
        } else if (jQuery.type(obj) === "array") {
            obj.forEach(function (element) {
                collectIds(element);
            });
        }
    }

    function setReferences(obj) {
        if (jQuery.type(obj) === "object") {
            for (var prop in obj) {
                if (jQuery.type(obj[prop]) === "object" &&
                    obj[prop].hasOwnProperty("$ref")) {
                    obj[prop] = hashOfObjects[obj[prop]["$ref"]];
                } else {
                    setReferences(obj[prop]);
                }
            }
        } else if (jQuery.type(obj) === "array") {
            obj.forEach(function (element, index, array) {
                if (jQuery.type(element) === "object" &&
                    element.hasOwnProperty("$ref")) {
                    array[index] = hashOfObjects[element["$ref"]];
                } else {
                    setReferences(element);
                }
            });
        }
    }

    // Set the max depth of your object graph because JSON.stringify will not be able to
    // serialize a large object graph back to 
    function setMaxDepth(obj, depth) {
        // If this is not an object or array just return there is no need to 
        // set its max depth.
        if (jQuery.type(obj) !== "array" && jQuery.type(obj) !== "object") {
            return obj;
        }

        var newObj = {};

        // If this object was an array we want to return the same type in order
        // to keep this variable's consistency.
        if (jQuery.type(obj) === "array") {
            newObj = [];
        }

        // For each object or array cut off its tree at the depth value by 
        // recursively diving into the tree.
        angular.forEach(obj, function (value, key) {
            if (depth == 1) {
                newObj = null;
            }
            else if (jQuery.type(value) === "array") {
                if (setMaxDepth(value, depth - 1)) {
                    newObj[key] = setMaxDepth(value, depth - 1)
                } else {
                    newObj = [];
                }
            } else if (jQuery.type(value) === "object") {
                if (setMaxDepth(value, depth - 1)) {
                    newObj[key] = setMaxDepth(value, depth - 1)
                } else {
                    newObj = [];
                }
            } else {
                newObj[key] = value;
            }
        }, newObj);

        return newObj;
    }

    function pointerParse(obj, depth) {
        var newObj = obj;

        hashOfObjects = {};
        collectIds(newObj);
        setReferences(newObj);

        if (depth) {
            newObj = setMaxDepth(newObj, depth);
        }

        return newObj;
    }
}
})();

使用法は次のようになります:

 var newObj = jsonPointerParseService.pointerParse(obj);

これはAngularサービスです。ですので、これらの参照を解決する必要があるコントローラーに注入し、それに応じてpointerParse()を呼び出します。

ただし、このオブジェクトをサーバーにポストバックする場合は、ブラウザーのJSON.stringify()呼び出しで循環参照を持つオブジェクトを処理できる必要があることに注意してください。デフォルトでは、私の場合、Angularは、循環参照でオブジェクトを文字列化できるようにするために必要なパラメーターを使用してJSON.stringify()を呼び出しません。したがって、この場合は、このオブジェクト全体ではなく、データのごく一部、またはこのようなオブジェクトを文字列化するための独自の呼び出しをインスタンス化します。

最後に、$ refを処理するのは面倒なので、配列のみを使用して参照処理を維持することにしました。これは私にはうまくいったようです。 $ valuesを使用して配列値にアクセスする必要がありましたが、参照の解決について心配する必要はなく、jsonへのすべてのオブジェクト循環参照をシリアル化しようとするサーバーの爆発について心配する必要もありませんでした。

json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;

json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Arrays;

更新:配列のみを解決しても、解決する必要がある参照がまだあります。それらのインスタンスでは、pointerParse()関数を使用してそれらを処理します。

3
txavier