スキップしてメイン コンテンツに移動

JSON のオブジェクトを ID でリンクする(5)

JSON のオブジェクトを ID でリンクする(4) - SakuraCrowd’s blog の続きです。


jsonId を利用しようとしていた仕様が変わってしまい、必要なくなったのでいったん保留します。


とはいっても、 {"$ref":"id1"} などの ID から、その ID を持つオブジェクトの参照への変換、逆変換の簡単なテストをクリアする実装もできたので、記念として gist にしました。長いので最後にはっときます。


 


jsonId の実装について


gist のコメントに書いてありますが、循環参照は jsonId の置換では問題にならないのでエラー検出はしないことにしました。


JSONPath でルートの "$" のときに jsonPath.eval の第一引数を対象にしたかったのですが、空の配列が返されてしまっていたので、その部分だけ条件文で特別に処理しました。


"$[0]" などはルート配列の1番目の要素の参照を取得できるので、 jsonPath.eval(...)[0]を使っています。


反省点


jsonId を使わない仕様をなぜ最初に気づけなかったか。


それは、なるべく作成する機能に汎用性を持たせたいという悪いくせがでたためだと思います。


tmx のプロパティの編集がやりづらいので、他の json ファイルに細かい設定を書いてそれを参照させようというのがきっかけでした。


しかし、実際に自分が必要とする設定は tmx のプロパティに CSV などで4つくらいのパラメータを書くだけですむものでした。


それがなぜか、もっと複雑なものも簡単に設定したいというあいまいな仕様になってしまいました。


実際に使うデータを処理できるだけで十分だということを念頭において仕様を考えるようにしたいです。


 


cocos2d-js-3.0-rc0 


が使えるようになりました(∩´∀`)∩ワーイ


setup.py をいちいち管理者権限で呼び出すのが面倒なのでバッチファイルを作りました。


2.2 のころと違って html を起動するだけというわけではなく、 cocos コマンドで実行するのが微妙に面倒ですが、ノードの親子関係に依存しないで奥行きを設定できたりするのが便利です。


project.json の module に extensions を追加しないと使えないのがわからず苦戦しましたが、ScrollView で簡単にスクロールできるようになってよかったです。


 


(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));
view raw sakuraCrowd.js hosted with ❤ by GitHub
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>
view raw SpecRunner.html hosted with ❤ by GitHub


jsonId の実装とテストケース


コメント

このブログの人気の投稿

QTableView で表を表示してみる

タイトルは駄洒落を狙っているわけではありません。 こんばんわ SakuraCrowd です。 今回は今作っているソフトの経過報告です。 最近のブログのパターンは、 「XXX作ったよ。これがスクリーンショットね。あとこんなこと思ったよ。」 という流れですが、 今日はできていないソフトの部分的な話なので、 いつもよりもプログラムちっくな話になると思います。(´Ծ_Ծ´)メガネノトキハマジメブッテル QTableView は GUIライブラリ Qt のクラスです。 それを Python で使うための PySide というライブラリを使っています。 某表計算ソフト っぽい表の GUI です。 このデータの日付が 09/01 なのでおそらくその日に   QTableView 使うぞ!(`・ω・´) とサンプルデータを作ったのでしょう。 Qt Designer という GUI エディタを使うとポトペタでウィンドウを設計できます。 選択できる GUI の中に QTableView と QTableWidget があります。 どちらも上のような表の GUI です。 QTableWidget は 簡単 に値をいれたりできます。 Qt Designer 上で直接編集 することができるので、 サンプルの表を簡単に作ることができます。 それに対して、 QTableView は Qt Designer 上では値を編集できません。たぶん。 QTableView の強みは MVC の構造 を使えることです。 名前のとおり QTableView は View です。 これにデータを管理している Model オブジェクトを設定して使います。 Model クラスを作る手間がかかりますが、 GUI の細かな操作をしなくても Model に応じた表を表示してくれます。 Model は QAbstractTableModel を 継承 して作ります。 コンストラクタで基底クラスの処理を呼び出し、いくつかの純粋仮想関数をオーバーライドします。 def __init__ ( self , parent= None , *args):...

HSPで画像の重ね合わせをしてみた

あいにくの曇り空だったが、 スーパームーン を少し見ることができた。 なんとなくだが、月明かりがいつもよりも強い気がする。 中秋の名月とほぼ同時に月が地球に 接近するのは稀らしいので何かありがたい(-人-) 先週ブログを書いていたときに、 ハロウィン にちなんだゲームを作りたいなー と思っていて、ふわっとした企画を考えて、少し作り始めた。 まだできるかどうかわからないけど、初めて HSP で絵を出せたのがうれしいのでブログを書いてみる。 HSP 自体はだいぶ前から知っていて、ちょっとしたGUIのツールを作ったりしていた。 GUIアプリケーションをここまで短く実装できる言語は自分の中ではこれが一番だと思う。 もっと短くできるかもしれないが、ビギナーな私でもこのくらい短くかける。 screen 0, 160, 64 // ウィンドウ作成 button "greet", *OnGreet // ボタン作成&イベント関連付け stop *OnGreet // イベント dialog "Hello!" stop バージョンアップして今では WebGL や  iOS や Android でも実行できる。 そのときは HSP3Dish という環境を使うために  #include "hsp3dish.as"  でスクリプトを読み込む。  参照: HSP3Dish プログラミングマニュアル・基本仕様ガイド 制限として、拡張プラグインやCOM/Variant型や外部DLL呼び出しやモジュール変数については未サポートのようだ。 ゲームでスプライトを用いるため es_set などのスプライト用の関数を使いたかったが、これは hspdx という拡張プラグインなので HSP3Dish には対応していないと思う。 そんな理由から、スプライト系の処理を自作しようと思う。 先週ちまちまとドット絵を描いたので、それを HSP のウィンドウに描画してみた。 なんかドット絵を作っている最中は、わりと良く思えたのに、 ウィンドウに出してみると何か微妙 (´・ω・`) ちなみに、キャラは4コマにも描いている大砲ゲーム「お団子キャノン」に出てくるキャラクターだ。 キャラの...

LibreOffice Writer 文書の差分 (WinMerge x TortoiseGit) + 社畜PCの原因と対策

お久しぶりです。皆様におかれましてはお風邪などをひかれてはいませんでしょうか。 春と秋だけあればいいのにヽ(`Д´)ノとつい思ってしまう SakuraCrowd です。 今日はいつものような製作日記ではなく、ちょっとした役立つメモを書きました。 タイトルにもあるとおり、 TortoiseGit への WinMerge の導入の仕方です。 今まではソースコードくらいしか差分で確認しなかったので、 TortoiseGit 標準の Diff ツールで問題なかったのですが、 LibreOffice の Writer が最近自分の中で便利だと話題になっていて、それを差分表示するためにちょっと調べてみました。 #Writer は、文章書いて、ちょっと絵をいれたり表を作るのに便利だと思います。 #リッチテキストのエディタを探していて、これが一番よさそうな気がしたので使ってます。 それとブログを書くときはあまり長く書かないつもりだった、 Win7 PC が社畜PCになってしまった際の原因と対策も後半に書きました。割と有用な情報かもしれませんので、時間がありましたらご覧下さい。 まずは TortoiseGit で Writer の odt ファイルを管理して、差分も普通に表示させる方法です。 WinMerge(+plugin) 導入手順 すでに TortoiseGit はインストールしてある前提ではなします。 1.信頼と実績の窓の杜様から WinMerge 日本語版をダウンロードします。 WinMerge - 窓の杜ライブラリ 私の PC は 64 ビット版なのでそちらを選びました。 2.WinMerge をインストールします。 フォルダを指定し普通にインストールできます。 インストール直前の設定で TortoiseGit をチェックしておくと自動的に TortoiseGit の利用する Diff ツールの設定を置き換えてくれるようです。 これの設定は TortoiseGit の設定の Diff ツールの項目で確認できます。 3.LibreOffice Writer のファイルを読むためのプラグインをダウンロードします。 ぐぐって出てくる英語版のDLサイトは応答がなかったりしましたが、日本...