jsonを検証できる正規表現を探しています。
私はRegexの初心者であり、Regexでの解析が悪いことを知っていますが、検証に使用できますか
jsonを検証できる正規表現を探しています。
私はRegexの初心者であり、Regexでの解析が悪いことを知っていますが、検証に使用できますか
回答:
最新の正規表現の実装では、再帰的な正規表現が可能です。これにより、完全なJSONシリアル化構造を検証できます。json.org仕様では、それは非常に簡単になります。
$pcre_regex = '
/
(?(DEFINE)
(?<number> -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
(?<boolean> true | false | null )
(?<string> " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
(?<array> \[ (?: (?&json) (?: , (?&json) )* )? \s* \] )
(?<pair> \s* (?&string) \s* : (?&json) )
(?<object> \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} )
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
)
\A (?&json) \Z
/six
';
PHPでPCRE関数を使用すると、非常にうまく機能します。Perlで変更せずに動作するはずです。もちろん他の言語にも適応できます。また、JSONテストケースでも成功します。
より単純なアプローチは、RFC4627のセクション6で指定されている最小限の整合性チェックです。ただし、これはセキュリティテストと基本的な無効性の予防策としてのみ意図されています。
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
一致しますが、最上位のJSON値は配列またはオブジェクトである必要があります。文字列またはスペースで使用できる文字セットにも多くの問題があります。
はい、正規表現は正規言語にのみ一致するというのはよくある誤解です。実際、PCRE関数は、通常の言語よりもはるかに多く一致でき、一部の非文脈自由言語にも一致できます。RegExpsに関するWikipediaの記事には、それに関する特別なセクションがあります。
JSONはPCREを使用していくつかの方法で認識できます。@marioは、名前付きサブパターンと後方参照を使用した1つの優れたソリューションを示しました。次に彼は、再帰的なパターン を使用した解決策があるはずだと指摘しました(?R)
。PHPで書かれたそのような正規表現の例を次に示します。
$regexString = '"([^"\\\\]*|\\\\["\\\\bfnrt\/]|\\\\u[0-9a-f]{4})*"';
$regexNumber = '-?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?';
$regexBoolean= 'true|false|null'; // these are actually copied from Mario's answer
$regex = '/\A('.$regexString.'|'.$regexNumber.'|'.$regexBoolean.'|'; //string, number, boolean
$regex.= '\[(?:(?1)(?:,(?1))*)?\s*\]|'; //arrays
$regex.= '\{(?:\s*'.$regexString.'\s*:(?1)(?:,\s*'.$regexString.'\s*:(?1))*)?\s*\}'; //objects
$regex.= ')\Z/is';
私が使用している(?1)
代わりに(?R)
、後者の言及ので、全体のパターンを、私たちは持っている\A
と\Z
サブパターンの内部で使用すべきではない配列を含みます。(?1)
最も外側の括弧でマークされた正規表現への参照(これが、最も外側の括弧( )
がで始まっていない理由?:
です)。したがって、正規表現は268文字になります:)
/\A("([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"|-?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?|true|false|null|\[(?:(?1)(?:,(?1))*)?\s*\]|\{(?:\s*"([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"\s*:(?1)(?:,\s*"([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"\s*:(?1))*)?\s*\})\Z/is
とにかく、これは実用的な解決策としてではなく、「テクノロジーのデモ」として扱われるべきです。PHPでは、json_decode()
関数を呼び出してJSON文字列を検証します(@Epcylonと同じように)。そのJSON を使用する場合(検証済みの場合)、これが最善の方法です。
\d
は危険です。多くの正規表現の実装\d
では、数字のUnicode定義と一致するだけ[0-9]
でなく、代替スクリプトが含まれています。
\d
PHPのPCREの実装では、Unicode番号と一致しないと思います。たとえば、٩
記号(0x669アラビア語の数字9)はパターンを使用して一致します#\p{Nd}#u
が、一致しません#\d#u
/u
フラグを使用しなかったためではありません。JSONはUTF-8でエンコードされています。適切な正規表現を使用するには、そのフラグを使用する必要があります。
u
修飾子を使用しました。以前のコメントのパターンをもう一度見てください:)文字列、数値、およびブール値は、トップレベルで正しく一致しています。ここに長い正規表現をquanetic.com/Regexに貼り付けて、自分で試すことができます
JSONの再帰的な性質(ネストされた{...}
-s)のため、正規表現はそれを検証するのには適していません。確かに、一部の正規表現フレーバーはパターン*を再帰的に照合できます(そのため、JSONを照合できます)が、結果のパターンは見づらく、プロダクションコードIMOで使用することはできません。
*ただし、多くの正規表現実装は再帰パターンをサポートしていません。一般的なプログラミング言語のうち、Perl、.NET、PHP、Ruby 1.9.2は再帰的なパターンをサポートしています。
@marioの答えを試しましたが、JSON.org(アーカイブ)からテストスイートをダウンロードし、4つの失敗したテスト(fail1.json、fail18.json、fail25.json、fail27)があったため、うまくいきませんでした。 json)。
私はエラーを調査し、それfail1.json
が実際に正しいことを発見しました(マニュアルのメモによると、RFC-7159の有効な文字列も有効なJSONです)。ファイルfail18.json
もそうではありませんでした、実際には正しい深くネストされたJSONが含まれているためです。
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
したがって、残りの2つのファイル:fail25.json
およびfail27.json
:
[" tab character in string "]
そして
["line
break"]
両方に無効な文字が含まれています。だから私はこのようにパターンを更新しました(文字列サブパターン更新):
$pcreRegex = '/
(?(DEFINE)
(?<number> -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
(?<boolean> true | false | null )
(?<string> " ([^"\n\r\t\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
(?<array> \[ (?: (?&json) (?: , (?&json) )* )? \s* \] )
(?<pair> \s* (?&string) \s* : (?&json) )
(?<object> \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} )
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
)
\A (?&json) \Z
/six';
したがって、json.orgからのすべての法的テストに合格することができます。
JSONのドキュメントを見ると、目的が適合性を確認することだけである場合、正規表現は3つの部分である可能性があります。
[]
またはで開始および終了します{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...すべて一緒に:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
JSON文字列に文字が含まれている場合は、と一致するように正規表現フレーバーのスイッチをnewline
使用する必要singleline
があります。これはすべての不良JSONで失敗するわけではありませんが、基本的なJSON構造が無効な場合は失敗します。これは、パーサーに渡す前に基本的な妥当性検証を行う簡単な方法です。.
newline
私はマリオのソリューションのRuby実装を作成しました、それは動作します:
# encoding: utf-8
module Constants
JSON_VALIDATOR_RE = /(
# define subtypes and build up the json syntax, BNF-grammar-style
# The {0} is a hack to simply define them as named groups here but not match on them yet
# I added some atomic grouping to prevent catastrophic backtracking on invalid inputs
(?<number> -?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?){0}
(?<boolean> true | false | null ){0}
(?<string> " (?>[^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ){0}
(?<array> \[ (?> \g<json> (?: , \g<json> )* )? \s* \] ){0}
(?<pair> \s* \g<string> \s* : \g<json> ){0}
(?<object> \{ (?> \g<pair> (?: , \g<pair> )* )? \s* \} ){0}
(?<json> \s* (?> \g<number> | \g<boolean> | \g<string> | \g<array> | \g<object> ) \s* ){0}
)
\A \g<json> \Z
/uix
end
########## inline test running
if __FILE__==$PROGRAM_NAME
# support
class String
def unindent
gsub(/^#{scan(/^(?!\n)\s*/).min_by{|l|l.length}}/u, "")
end
end
require 'test/unit' unless defined? Test::Unit
class JsonValidationTest < Test::Unit::TestCase
include Constants
def setup
end
def test_json_validator_simple_string
assert_not_nil %s[ {"somedata": 5 }].match(JSON_VALIDATOR_RE)
end
def test_json_validator_deep_string
long_json = <<-JSON.unindent
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"id": 1918723,
"boolean": true,
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
JSON
assert_not_nil long_json.match(JSON_VALIDATOR_RE)
end
end
end
「文字列と数字」については、数字の部分的な正規表現だと思います。
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
代わりに:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
数値の小数部はオプションであり、また、大括弧の間に特別な意味があるため、-
記号をエスケープする方がおそらく安全です。[+-]
\d
は危険です。多くの正規表現の実装\d
では、数字のUnicode定義と一致するだけ[0-9]
でなく、代替スクリプトが含まれています。
JSON配列の末尾のコンマが原因でPerl 5.16がハングしました。これはおそらくバックトラックを続けたためです。私はバックトラック終了ディレクティブを追加する必要がありました:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
このように、「オプション」ではない構成(*
または?
)を識別した後は、それをバックトラックして、別の構成として識別しようとすべきではありません。
上記のように、使用する言語にJSONライブラリが付属している場合は、それを使用して文字列をデコードし、失敗した場合に例外/エラーをキャッチします。言語がそうでない場合(FreeMarkerでこのようなケースがあっただけ)、次の正規表現は少なくともいくつかの非常に基本的な検証を提供できます(PHP / PCREがより多くのユーザーにテスト可能/使用可能になるように書かれています)。受け入れられているソリューションほど簡単ではありませんが、恐ろしいものでもありません=):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
簡単な説明:
// we have two possibilities in case the string is JSON
// 1. the string passed is "just" a JSON object, e.g. {"item": [], "anotheritem": "content"}
// this can be matched by the following regex which makes sure there is at least a {" at the
// beginning of the string and a } at the end of the string, whatever is inbetween is not checked!
^\{\s*\".*\}$
// OR (character "|" in the regex pattern)
// 2. the string passed is a JSON array, e.g. [{"item": "value"}, {"item": "value"}]
// which would be matched by the second part of the pattern above
^\[\n?\{\s*\".*\}\n?\]$
// the s modifier is used to make "." also match newline characters (can happen in prettyfied JSON)
意図せずこれを壊すような何かを見逃した場合は、コメントをいただければ幸いです。
key(string):value(string、integer、[{key:value}、{key:value}]、{key:value})を検証します
^\{(\s|\n\s)*(("\w*"):(\s)*("\w*"|\d*|(\{(\s|\n\s)*(("\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))((,(\s|\n\s)*"\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))*(\s|\n\s)*\}){1}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d*|(\{(\s|\n\s)*(("\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))((,(\s|\n\s)*"\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))*(\s|\n\s)*\}){1}))*(\s|\n)*\}$
{
"key":"string",
"key": 56,
"key":{
"attr":"integer",
"attr": 12
},
"key":{
"key":[
{
"attr": 4,
"attr": "string"
}
]
}
}