Skip to content

Instantly share code, notes, and snippets.

@sakura-crowd
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sakura-crowd/b736832f937d77dfd947 to your computer and use it in GitHub Desktop.
Save sakura-crowd/b736832f937d77dfd947 to your computer and use it in GitHub Desktop.
jsonId の実装とテストケース
(function() {
var root = this;
var sakuraCrowd = function(obj) {
if (obj instanceof sakuraCrowd) return obj;
if (!(this instanceof sakuraCrowd)) return new sakuraCrowd(obj);
this._wrapped = obj;
};
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = sakuraCrowd;
}
exports.sakuraCrowd = sakuraCrowd;
} else {
root.sakuraCrowd = sakuraCrowd;
}
// Current version.
sakuraCrowd.VERSION = '0.1';
// Json Functions
// --------------------
/// JSON でオブジェクトの参照を表現する拡張機能のコレクションです。
///
/// 参照されるオブジェクトを参照可能オブジェクト、オブジェクトの参照を表す値を参照オブジェクトと呼びます。
/// 参照可能オブジェクトは ID のプロパティを持ちます。値は文字列です。既定の名前は refId です。
/// 参照オブジェクトは参照のプロパティを持ちます。値は ID の文字列です。既定の名前は $ref です。
///
/// 用例
/// jsonStr = '[{"greeting":{"$ref":"greet1"}}, {"refId":"greet1", "message":"hello"}]';
/// target = JSON.parse(jsonStr);
/// table = sakuraCrowd.jsonId.refToObj(target); // target[0].greeting === target[1]
///
/// 読み込んだオブジェクトのすべてのプロパティの中で参照オブジェクトをそれが指す参照可能オブジェクトに置き換えます。
/// 上の例では $[0] のオブジェクトの "greeting" というプロパティの参照オブジェクトが $[1] のオブジェクトに置き換えられます。
///
/// sakuraCrowd.jsonId.objToRef(target); // target == [{"greeting":{"refId":"greet1", "message":"hello"}, {"$ref":"greet1"}}];
///
/// 同じ参照可能オブジェクトが値として出てきた場合は参照オブジェクトに置き換えます。
/// 参照可能オブジェクトの定義位置は最初に出てきた位置となってしまいます。
///
/// sakuraCrowd.jsonId.objToRef(target, table); // target == [{"greeting":{"$ref":"greet1"}}, {"refId":"greet1", "message":"hello"}]
/// 位置を指定するには、refToObj の戻り値の ID テーブルを指定します。
/// この例では greet1 の定義位置が $[1] であることが ID テーブルに指定されるため、 $[0].greeting ではなく $[1] で定義されます。
sakuraCrowd.jsonId = {};
/// ID とオブジェクトの関連を表す情報です。
sakuraCrowd.jsonId.Info = function(root, table){
if (typeof table === 'undefined') var table = {};
/// root のオブジェクトです。これより上位のオブジェクトは表せません。
this.root = root;
/// ID をキーにした辞書です。値ではオブジェクトを表す JSONPath を設定します。
this.table = table;
};
/// JSONPath に新しいキーを追加します。引数 arg が数値ならば [arg], 文字列ならば .arg を追加した新しい文字列を返します。
/// @param {string} jsonPath JSONPath の文字列です。
/// @param {string|number} key プロパティ名または要素番号です。 JSONPath に追加されます。
/// @returns {string} key を追加した新しい JSONPath の文字列です。
sakuraCrowd.jsonId.concatJsonKey = function(jsonPath, key) {
if (_.isNumber(key) == true) {
var jsonPath1 = jsonPath.concat('[' + key + ']');
}
else {
var jsonPath1 = jsonPath.concat('.' + key);
}
return jsonPath1;
};
/// 参照可能オブジェクトであることを判別します。
/// @param {object} 判別する対象です。
/// @param {string} ID プロパティの名前です。
sakuraCrowd.jsonId.isReferenceableObject = function(obj, idName) {
if (_.isObject(obj) == false) {return false;}
return _.has(obj, idName);
};
/// 参照オブジェクトであることを判別します。
/// @param {object} 判別する対象です。
/// @param {string} 参照プロパティの名前です。
sakuraCrowd.jsonId.isReferenceObject = function(obj, refName) {
if (_.isObject(obj) == false) {return false;}
return _.has(obj, refName);
};
/// @param {Array|object} target この中のすべての object 型の値を処理します。
/// @param {string} path オブジェクトの上位の JsonPath です。
/// @param {string} idName ID として識別するプロパティ名を指定します。既定では 'id' です。
/// @returns {object} ID がキーで、値はオブジェクトの位置を表す JSONPath です。
sakuraCrowd.jsonId._createIdObjectTable = function(target, path, idName) {
if (typeof idName === 'undefined') var idName = 'id';
if (typeof path === 'undefined') var path = '$';
var table = {};
// ID を持つオブジェクトならば table に記録する。
if (_.isObject(target) == true) {
if (_.has(target, idName) == true) {
var tmp = {};
tmp[target[idName]] = path;
_.extend(table, tmp);
}
}
// オブジェクトと配列以外は空の辞書を返す。
else if (_.isArray(target) != true) {
return table;
}
// オブジェクトと配列は、その要素の値について再帰的に処理する。結果は合成する。
_.each(target, function(v, k, l) {
path1 = sakuraCrowd.jsonId.concatJsonKey(path, k);
_.extend(table, sakuraCrowd.jsonId._createIdObjectTable(v, path1, idName));
});
return table;
};
/// @param {Array|object} target この中のすべての参照型の値を参照先のオブジェクトの値へ変換します。
/// @param {string} path オブジェクトの上位の JsonPath です。
/// @param {object} info sakuraCrowd.jsonId.Info です。
/// @param {string} refName 参照を指定するプロパティ名を指定します。既定では '$ref' です。これを持つオブジェクトを参照型と呼びます。
/// @param {array} uppers 上位のオブジェクトのリストです。循環参照を検出するために内部で使います。
sakuraCrowd.jsonId._linkObjectById = function(target, path, info, refName, uppers) {
if (typeof refName === 'undefined') var refName = '$ref';
if (typeof uppers === 'undefined') var uppers = [];
if (typeof path === 'undefined') var path = '$';
var uppers1 = uppers.concat(); // 配列の前の要素の情報ははぶく。要素ごとの関数呼び出しで
uppers1.push(target);
// オブジェクトまたは配列の要素の値をすべて確認して、参照型ならば値を参照先のオブジェクトに変更する。
if (_.isObject(target) == true || _.isArray(target) == true) {
_.each(target, function(v, k, l) {
// path の追加
path1 = sakuraCrowd.jsonId.concatJsonKey(path, k);
// 参照型のオブジェクトの場合は、参照先のオブジェクトに変更します。
if (sakuraCrowd.jsonId.isReferenceObject(v, refName) == true) {
if (_.has(info.table, v[refName]) == false) {
throw v[refName] + " は不明な ID です。";
}
objPath = info.table[v[refName]];
refObj = jsonPath.eval(info.root, objPath)[0];
if (typeof refObj === "undefined") {
throw v[refName] + " のパス " + objPath + "は無効です。";
}
l[k] = refObj; // l[k] の値を v から refObj に変更する。
var uppers2 = uppers1.concat();
uppers2.push(refObj);
sakuraCrowd.jsonId._linkObjectById(refObj, path1, info, refName, uppers2);
}
// 参照型以外の場合は再帰的に処理する。
else {
sakuraCrowd.jsonId._linkObjectById(v, path1, info, refName, uppers1);
}
});
}
return;
};
/// ID がついているオブジェクトの情報を収集します。
/// @param {object|array} target ルートのオブジェクトです。
/// @param {string} offsetJSONPath オブジェクトの上位の JsonPath です。
/// @param {string} idName ID として識別するプロパティ名を指定します。既定では 'id' です。
/// @returns {object} sakuraCrowd.jsonId.Info です。
sakuraCrowd.jsonId.createInfo = function(target, offsetJSONPath, idName) {
if (typeof offsetJSONPath == "undefined") var offsetJSONPath = "$";
if (typeof info == "undefined") var info = new sakuraCrowd.jsonId.Info(target);
if (info.root !== target) throw "info.root !== target";
var target1 = null;
if (offsetJSONPath === "$") {
target1 = target;
} else {
target1 = jsonPath.eval(target, offsetJSONPath)[0];
}
// ID とオブジェクトの辞書を作成する。
var table1 = sakuraCrowd.jsonId._createIdObjectTable(target1, offsetJSONPath, idName);
return {"root":target, "table":table1};
};
/// 参照オブジェクトから参照先オブジェクトへ値を変更します。
/// @param {Array|object} target この中のすべての object 型の要素の値を処理します。
/// @param {string} offsetJSONPath target をルートとして処理を開始する位置を JSONPath で指定します。既定では "$"です。
/// @param {object} info sakuraCrowd.jsonId.Info です。処理対象ではない場所の ID を指定できます。 info.root === target でなければいけません。既定では {} です。
/// @param {string} idName ID として識別するプロパティ名を指定します。既定では 'id' です。
/// @param {string} refName 参照を指定するプロパティ名を指定します。既定では '$ref' です。
/// @returns {object} sakuraCrowd.jsonId.Info です。変換する際に取得した情報です。 info を含みます。
sakuraCrowd.jsonId.refToObj = function(target, offsetJSONPath, info, idName, refName) {
if (typeof offsetJSONPath == "undefined") var offsetJSONPath = "$";
if (typeof info == "undefined") var info = new sakuraCrowd.jsonId.Info(target);
if (info.root !== target) throw "info.root !== target";
var target1 = null;
if (offsetJSONPath === "$") {
target1 = target;
} else {
target1 = jsonPath.eval(target, offsetJSONPath)[0];
}
// ID とオブジェクトの辞書を作成する。
var table1 = sakuraCrowd.jsonId._createIdObjectTable(target1, offsetJSONPath, idName);
// 処理対象から得た ID の情報を引数の情報に追加する。
_.extend(info.table, table1);
// info.table をもとに参照を参照先に置き換える。
sakuraCrowd.jsonId._linkObjectById(target1, offsetJSONPath, info, refName);
return info;
};
/// プロパティの値が参照可能オブジェクトの場合は参照オブジェクトへ変更します。
/// 参照可能オブジェクトとしての定義は、最初に出現した場所か info で指定された場所です。
sakuraCrowd.jsonId.objToRef = function(target, info, idName, refName, path) {
if (typeof info === 'undefined') var info = new sakuraCrowd.jsonId.Info(target);
if (typeof idName === 'undefined') var idName = 'id';
if (typeof refName === 'undefined') var refName = '$ref';
if (typeof path === 'undefined') var path = '$';
// オブジェクトまたは配列の要素の値をすべて確認して、参照型ならば値を参照先のオブジェクトに変更する。
if (_.isObject(target) == true || _.isArray(target) == true) {
_.each(target, function(v, k, l) {
// path の追加
path1 = sakuraCrowd.jsonId.concatJsonKey(path, k);
// 参照可能オブジェクトの場合は、それを定義するパス以外の場所のものは参照オブジェクトに置き換える。
if (sakuraCrowd.jsonId.isReferenceableObject(v, idName) == true) {
if (_.has(info.table, v[idName]) == false) {
info.table[v[idName]] = path1; // 空のinfoの場合などは、最初の場所を定義するパスとして追加する。
}
var originalPath = info.table[v[idName]];
if (path1 != originalPath) {
var tmp = {};
tmp[refName] = v[idName];
l[k] = tmp;
}
}
// 参照型以外の場合は再帰的に処理する。
else {
sakuraCrowd.jsonId.objToRef(v, info, idName, refName, path1);
}
});
}
return;
};
}.call(this));
describe('sakuraCrowd.jsonId.refToObj', function() {
it("参照オブジェクトを参照先オブジェクトに変換する", function() {
var target = [
{"id":"id1", "value":10},
{"$ref":"id1"}
];
sakuraCrowd.jsonId.refToObj(target);
expect(target.length).toEqual(2);
expect(target[0]).toBe(target[1]);
});
it("指定したオブジェクトの下位の場所から変換する", function() {
var target = [
{"id":"id1", "value":10, "other":{"$ref":"id2"}},
{"id":"id2",
"other":[
{"id":"id3", "value":30},
{"$ref":"id3"}
]
},
];
var target2 = [
{"id":"id1", "value":10, "other":{"$ref":"id2"}},
{"id":"id2",
"other":[
{"id":"id3", "value":30},
{"$ref":"id3"}
]
},
];
target2[1].other[1] = target2[1].other[0];
var info = sakuraCrowd.jsonId.refToObj(target, "$[1]");
expect(target).toEqual(target2);
expect(target[1].other[1]).toBe(target[1].other[0]);
});
it("指定したオブジェクトの下位の場所から変換する。 info に設定した外部の参照にも対応する。", function() {
var target = [
{"id":"id1", "value":10},
{"id":"id2",
"other":[
{"id":"id3", "value":30},
{"$ref":"id1"}
]
},
{"$ref":"id3"},
];
var target2 = [
{"id":"id1", "value":10},
{"id":"id2",
"other":[
{"id":"id3", "value":30},
{"$ref":"id1"}
]
},
{"$ref":"id3"},
];
target2[1].other[1] = target[0];
var info1 = sakuraCrowd.jsonId.createInfo(target, "$[0]");
var info2 = sakuraCrowd.jsonId.refToObj(target, "$[1].other", info1);
expect(target).toEqual(target2);
});
it("参照の ID に対応するパスがない場合はエラーとなる。", function() {
var target = [
{"$ref":"id1"},
];
expect(sakuraCrowd.jsonId.refToObj.bind(null, target)).toThrow("id1" + " は不明な ID です。");
});
it("参照の ID に対応するパスが無効な場合はエラーとなる。", function() {
var target = [
{"$ref":"id1"},
];
var info1 = {"root":target, "table":{"id1":"$.hoge"}};
expect(sakuraCrowd.jsonId.refToObj.bind(null, target, "$", info1)).toThrow("id1" + " のパス " + "$.hoge" + "は無効です。");
});
});
describe('sakuraCrowd.jsonId.objToRef', function() {
it("参照可能オブジェクトを参照オブジェクトに変換する(infoあり)", function() {
var target = [
{"$ref":"id1"},
{"id":"id1", "value":10}
];
var target2 = [
{"$ref":"id1"},
{"id":"id1", "value":10}
];
var expectedInfo = {
"root":target,
"table":{
"id1":"$[1]"
}
};
info = sakuraCrowd.jsonId.refToObj(target);
sakuraCrowd.jsonId.objToRef(target, info);
expect(target).toEqual(target2);
expect(info.table).toEqual(expectedInfo.table);
expect(info.root).toBe(expectedInfo.root);
});
it("参照可能オブジェクトを参照オブジェクトに変換する(infoなし)", function() {
// 参照オブジェクトにするときに、 ID に対応した JSONPath の情報がなければ、
// 最初に見つけた参照可能オブジェクトを定義として残します。それ以降は、参照オブジェクトに置換します。
var target = [
{"$ref":"id1"},
{"id":"id1", "value":10}
];
var target2 = [
{"id":"id1", "value":10},
{"$ref":"id1"}
];
info = sakuraCrowd.jsonId.refToObj(target);
sakuraCrowd.jsonId.objToRef(target);
expect(target).toEqual(target2);
});
});
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Jasmine Spec Runner v2.0.0</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css">
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="src/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="src/underscore-min.js"></script>
<script type="text/javascript" src="src/jsonpath.js"></script>
<script type="text/javascript" src="src/sakuraCrowd.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/sakuraCrowd.spec.js"></script>
</head>
<body>
</body>
</html>
@sakura-crowd
Copy link
Author

jsonId の実装と jasmine 用テストケースです。
大きな変更点としては、循環参照をエラーにしなくしたことです。
そのため、 uppers という循環参照をチェックする引数はまだ消していませんが不要になりました。
作りかけですが、仕様がかわって使わなくなって保留するので、足跡として残しておきます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment