まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2013/06/13(木)JSONを自力でパースするperlスクリプト【pure-Perl】

正しいもののみ正しくパースされるスクリプトです。

  • 外部モジュール不要。
  • 正しくないものは解析できるところまで動きます。
  • かっこ「{[」と「]}」の対応は見てません(閉じかっこの入れ違えがあっても動きます)
  • ライセンスはWTFPLですが、一言「使ったよ」ってコメントくれると嬉しいです。

バグがあったら具体的なデータと共にコメントください。

スクリプト

外部モジュールにしない場合は、変数の初期化を perse_join の中に入れてください。

#-------------------------------------------------
# Copyright (C)nabe@abk  
#-------------------------------------------------
my %json_esc = (
"/" => "/",
'"' => '"',
"\\" => "\\",
"b" => "\x08",
"f" => "\x0c",
"n" => "\n",
"r" => "\r",
"t" => "\t"
);
my %json_val = (
'true'  => 1,
'false' => 0,
'null'  => undef
);
sub parse_json {
	my $json = shift;
	my @str;
	$json =~ s/[\x00-\x01]//g;
	$json =~ s/\\"/\x01/g;
	$json =~ s/"([^"]*)"/push(@str,$1),"\x00$#str\x00"/eg;
	# 文字列の処理
	foreach(@str) {
		$_ =~ s|\\([/\\bfnrt])|$json_esc{$1}|g;
		$_ =~ s/\x01/"/g;
		$_ =~ s/\\u([0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z])/
			my $x = hex($1);
			if ($x < 2) { 
				"";
			} elsif ($x < 0x100) { 
				chr($x);
			} else {
				# UTF-16 to UTF-8
				  chr(0xE0 | (($x>>12) & 0x0f))
				. chr(0x80 | (($x>> 6) & 0x3f))
				. chr(0x80 | ( $x      & 0x3f));
			}
		/eg;
	}
	$json =~ s/\s*//g;
	#-------------------------------------------------------
	# JSON解析
	#-------------------------------------------------------
	my @ary;
	foreach(split(/,/, $json)) {
		while($_ =~ /^(.*?)([\[\]\{\}])(.*)$/) {
			if ($1 ne '') { push(@ary, $1); }
			push(@ary, $2);
			$_ = $3;
		}
		if ($_ ne '') { push(@ary, $_); }
	}
	my @stack;
	my $ret = (shift(@ary) eq '{') ? {} : [];
	my $c = $ret;
	eval {
	while(@ary) {
		my $v = shift(@ary);
		if ($v eq '}' || $v eq ']') {
			$c = pop(@stack);
			next;
		}
		# ハッシュの場合
		if (ref($c) eq 'HASH') {
			my $x = index($v, ':');
			my $key = substr($v, 0, $x);
			my $val = substr($v, $x+1);
			$key =~ s/^\x00(\d+)\x00$/$str[$1]/;
			if (exists $json_val{$val}) {
				$val = $json_val{$val};
			} else {
				$val =~ s/^\x00(\d+)\x00$/$str[$1]/;
			}
			if ($ary[0] eq '{') {
				shift(@ary);
				push(@stack, $c);
				$c = $c->{$key} = {};
			} elsif ($ary[0] eq '[') {
				shift(@ary);
				push(@stack, $c);
				$c = $c->{$key} = [];
			} else {
				$c->{$key} = $val;
			}
			next;
		}
		# 配列の場合
		if ($v eq '{') {
			push(@stack, $c);
			push(@$c, {});
			$c = $c->[ $#$c ];
		} elsif ($v eq '[') {
			push(@stack, $c);
			push(@$c, []);
			$c = $c->[ $#$c ];
		} elsif (exists $json_val{$v}) {
			push(@$c, $json_val{$v});
		} else {
			$v =~ s/^\x00(\d+)\x00$/$str[$1]/;
			push(@$c, $v);
		}
	}
	}; # eval

	return $ret;
}

パース結果確認用のスクリプト

sub nest_hash_to_string {
	my $data = shift;
	my $sp   = shift || '';
	my $ret = '';
	if (ref($data) eq 'HASH') {
		foreach(sort(keys(%$data))) {
			my $k = $_;
			my $v = $data->{$_};
			if (ref($v) eq 'ARRAY') {
				$ret .= "$sp$k=[\n";
				$ret .= &nest_hash_to_string($v, "  $sp");
				$ret .= "$sp]\n";
				next;
			}
			if (ref($v) eq 'HASH') {
				$ret .= "$sp$k={\n";
				$ret .= &nest_hash_to_string($v, "  $sp");
				$ret .= "$sp}\n";
				next;
			}
			$ret .= "$sp$k=$v\n";
		}
	} else {
		foreach(@$data) {
			if (ref($_) eq 'ARRAY') {
				$ret .= "$sp\[\n";
				$ret .= &nest_hash_to_string($_, "  $sp");
				$ret .= "$sp]\n";
				next;
			}
			if (ref($_) eq 'HASH') {
				$ret .= "$sp\{\n";
				$ret .= &nest_hash_to_string($_, "  $sp");
				$ret .= "$sp}\n";
				next;
			}
			$ret .= "$sp$_\n";
		}
	}
	return $ret;
}

速度比較

twitterにstatus updateしたときに戻ってきたJSONでテストしました。

  JSON::PP:  4 wallclock secs ( 3.85 usr +  0.00 sys =  3.85 CPU) @ 259.74/s (n=1000)
  JSON::XS:  0 wallclock secs ( 0.03 usr +  0.00 sys =  0.03 CPU) @ 33333.33/s (n=1000)
            (warning: too few iterations for a reliable count)
parse_json:  1 wallclock secs ( 1.22 usr +  0.00 sys =  1.22 CPU) @ 819.67/s (n=1000)

XSはさすがに圧倒的ですね(苦笑)