JSONの整形出力2007年05月15日 15時02分35秒

JSONの可読性

先のエントリで、オンラインJSONエディタのことを書いたが、それと関連。

dara-jは、個人的なスクリプト(WSH/HTA/ASP)をちょろちょろ書いているが、設定を要するものは今は大体JSONにしている。

ところがjson.jsなどのライブラリで出力すると、不要な空白や改行が除去されるため可読性が極端に悪く、テキストエディタでちょっと編集というわけには行かない。まぁそのおかげで処理が速いんだけど。

そこで、ちょっとでも編集しやすいようにと思い、整形出力するライブラリを書いてみた。

Json.Formatter

コードはちょっと大きめで申し訳ないが、以下のような感じ。

// 文字列を引用符で囲う
String.prototype.quote = function() {
	var s = this;
	var a = [
		{ match : /\\/g, replace : "\\\\" },
		{ match : /\f/g, replace : "\\f" },
		{ match : /\n/g, replace : "\\n" },
		{ match : /\r/g, replace : "\\r" },
		{ match : /\t/g, replace : "\\t" },
		{ match : /\v/g, replace : "\\v" },
		{ match : /"/g, replace : "\\\"" }
	];
	for(var i = 0; i < a.length; i++) {
		var value = a[i];
		s = s.replace( value.match, value.replace );
	};
	return [ "\"", s, "\"" ].join("");
}

if( typeof( Json ) == "undefined" ) {
	var Json = {
	}
}

Json.Formatter = function() {
	this.initialize.apply( this, arguments );
}
Json.Formatter.prototype = {
	value : false,
	
	// 初期化処理
	initialize : function(obj) {
		var indent_char = Json.Formatter.indentString || "\t";
		var name = arguments[1] ? arguments[1].toString().quote() : null;
		var indent = isNaN( arguments[2] ) ? 0 : Number( arguments[2] );
		
		var current_indent = Json.Formatter.createString( indent_char, indent );
		var buffer = [ current_indent ];
		
		if( name ) {
			buffer.push( name );
			buffer.push( " : " );
		}
		
		var type = typeof( obj );
		if( type == "undefined" ) {
			// undefined
			// なにもしない
			
		} else if( obj == null ) {
			// null
			// プロパティの値の場合のみ'null'を追加
			if( name ) buffer.push( "null" );
			
		} else if( type == "string" ) {
			// string
			buffer.push( obj.quote() );
			
		} else if( type == "number" ) {
			// number
			buffer.push( obj.toString() );
			
		} else if( type == "boolean" ) {
			// boolean
			buffer.push( ( !! obj ).toString() );
			
		} else if( obj instanceof Array ) {
			// array
			var hasProp = obj.length > 0;
			var lastIndex = obj.length - 1;
			
			buffer.push( "[" );
			buffer.push( hasProp ? "\r\n" : "" );
			// 要素を再帰処理
			for(var index = 0; index < obj.length; index++) {
				var v = obj[ index ];
				if( v != null ) {
					buffer.push( new Json.Formatter( v, null, indent + 1 ).value );
					buffer.push( index < lastIndex ? "," : "" );
					buffer.push( "\r\n" );
				}
			}
			if( buffer.length > 2 && buffer[ buffer.length - 2 ] == "," ) buffer[ buffer.length - 2 ] = "";
			buffer.push( hasProp ? current_indent : "" );
			buffer.push( "]" );
			
		} else {
			// object
			var hasProp = false;
			for(var key in obj) {
				if( typeof(obj) != "function" ) {
					hasProp = true;
					break;
				}
			}
			
			buffer.push( "{" );
			buffer.push( hasProp ? "\r\n" : "" );
			// プロパティを再帰処理
			for(key in obj) {
				if( typeof(obj[key]) == "function" ) continue;
				buffer.push( new Json.Formatter( obj[key], key, indent + 1 ).value );
				buffer.push( ",\r\n" );
			}
			if( hasProp ) buffer.length--;
			
			buffer.push( hasProp ? "\r\n" : "" );
			buffer.push( hasProp ? current_indent : "" );
			buffer.push( "}" );
			
		}
		
		// valueプロパティ確定
		this.value = buffer.join("");
		
		var self = this;
		this.toString = function() {
			return self.value;
		}
	}
}
Json.Formatter.createString = function(c, l) {
	var result = new Array( l );
	for(var i = 0; i < l; i++) result[ i ] = c;
	return result.join("");
}
Json.Formatter.indentString = "\t";
他のライブラリには依存していないのでこれ単体で使用できるが、文字列を引用符でくくるためにString.prototypeを拡張している。気になる向きは関数化するなどすれば問題ないと思う。コード中で使用しているのは2箇所のみだし。

使い方

コンストラクタにJSON化したいオブジェクトを渡してvalueプロパティを参照するだけ。

var obj = { a : "property A", b : 30, c : true, d : [ 0, 1, 2 ] };
var formatter = new Json.Formatter( obj );
alert( formatter.value );
// 以下のような出力を得る
/*
{
	"a" : "property A",
	"b" : 30,
	"c" : true,
	"d" : [
		0,
		1,
		2
	]
}
*/
インデントはタブで行われるが、Json.Formatter.indentStringプロパティを変更することで任意の文字列にできる。

サンプル

テキストボックスにJSONを書き込んで[format]をクリックすると整形します。整形結果をalertではなく元のテキストボックスに放り込むよう修正しました。(2007.5.15 19:19)

注意など

重い。小さなオブジェクトなら問題ないけど、100行単位のArrayとか、ネストが深いObjectとかをかませるとかなり重い。だれか効率よくなるようにアドバイスください><

あと、このライブラリはオブジェクト → JSONのみのライブラリです。JSON → オブジェクトは他のライブラリを併用するか、漢らしくeval()するかで対応してください。dara-jはjson.js使うけど。

ま、あまり需要ないかな。

StartForceとFirebug2007年05月15日 18時56分04秒

EditGrid使えるよ

5/12のエントリStartForceをFirefoxで動かした場合に

アップロードした.xlsファイルまで開ける表計算アプリがあるんだけど、Firefoxで開くとFirefox自体まで道連れにしてフリーズしてしまう。
ということを書いた。

ところで今日、IT戦記さんとこの記事で「Livedoor Readerなんかを使うとFirebugが壊れる原因」てのが書いてあり、

ここを
while (win && win.parent != win)
こういう風に修正
while (win && win.parent != win && win.parent instanceof Window)
なんてことが書いてあった。

これみてなんとなく「ありゃ、Firebugと相性悪いか?」と思い、Firebugを切ってStartForceにログイン、EditGrid(表計算アプリ)を立ち上げてみた。おお、ちゃんと起動できるじゃん

LDRと同じ原因か?

ああ、ひょっとしたら根っこは同じかも、と思い、IT戦記さんとこの修正を自分の環境に適用してみた。

が、状況は変わらず、ブラウザ本体を道連れにフリーズ。ふむ。

せめてInspect Elementくらい...

EditGridには効果がなかったものの、ひょっとしたら「Inspect Element」でのスクリプトエラー多発にはなにがしかの効果があったかも、と思って試してみたがあえなく沈没。ただし、Firebugのメニューから(Inspect Elementではなく)Command Lineを実行する分には問題ないので、「あー、なんかしょっちゅうPOSTしてるよー、なんにもしてないのに」とかを確認することはできたが、うっかりしてフリーズすると腹立たしいのでStartForceのサイト(www1.startforce.jp)をFirebugから除外することにした。

結論

  • FirebugをenabledにしているときにEditGridを立ち上げてはいけない
  • 間違ってもInspect Elementしてはいけない
  • なんでFirefoxのときはコンテキストメニューが働かない?