JSONを検証する正規表現


89

jsonを検証できる正規表現を探しています。

私はRegexの初心者であり、Regexでの解析が悪いことを知っていますが、検証に使用できますか


31
なぜ別の検証ステップに悩むのですか?ほとんどの言語には、JSONを解析できるJSONライブラリがあり、それを解析できる場合は有効でした。そうでない場合は、ライブラリが教えてくれます。
Epcylon

あなたはそれを検証するために、テキストを解析する必要があります...
ケン

3
@mario-わからない...私はすべて正規表現を乱用することに賛成しており、「正規表現は正規のものと一致しなければならない」という誤解に対するあなたの異議に非常に同情しています。ここでの最良の答えは、本当にEpcylonのコメントです...(この議論はチャットに属しているのでしょうか?)
Kobi

1
別の実用的な使用例は、より大きな文字列内でJSON式を見つけることです。「この文字列はここではJSONオブジェクトですか」と単に質問したい場合は、はい、JSON解析ライブラリがおそらくより良いツールです。しかし、より大きな構造内でJSONオブジェクトを見つけることはできません。
マークアメリー2014

1
これは答えではありませんが、CrockfordのJSON-jsライブラリのこの部分を使用できます。4つの正規表現を使用し、巧妙な方法でそれらを組み合わせます。
imgx64 '10 / 10/19

回答:


182

はい、完全な正規表現の検証が可能です。

最新の正規表現の実装では、再帰的な正規表現が可能です。これにより、完全な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検証

より単純なアプローチは、RFC4627のセクション6で指定されている最小限の整合性チェックです。ただし、これはセキュリティテストと基本的な無効性の予防策としてのみ意図されています。

  var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
         text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
     eval('(' + text + ')');

22
+1正規表現の構文を取得できず、それを悪用する理由として誤用している人々からは、世の中に非常に多くの問題があります:(
NikiC

8
@mario、私がthe-naysayers-departmentにいると思うかどうかはわかりませんが、私は違います。「ほとんどの最新の正規表現の実装では再帰的な正規表現が可能です」という文は非常に議論の余地があることに注意してください。AFAIK、Perl、PHPおよび.NETのみが再帰パターンを定義する機能を備えています。私はそれを「ほとんど」とは呼びません。
Bart Kiers

3
@バート:はい、それは間違いなく議論の余地があります。最も皮肉なことに、Javascriptの正規表現エンジンは、そのような再帰的な正規表現を使用してJSONを検証することはできません(または複雑な回避策を使用する場合のみ)。したがって、正規表現== posix正規表現の場合、それはオプションではありません。それにもかかわらず、現代の実装で実行できるのは興味深いことです。実際の使用例はほとんどありません。(しかし、本当のところ、libpcreはどこでも普及しているエンジンではありません。)-また、記録として:私は合成リバーサルバッジを望んでいましたが、バンドワゴンの賛成票をいくつかもらえないことがそれを妨げています。:/
マリオ

4
いいえ。私はポピュリストバッジの後にいました。20票が必要ですが、それでもあなたの回答には10票必要です。それで反対にあなたの質問への反対票は私の利益になりません。
マリオ

2
さて、さらに見ると、この正規表現には他にも多くの問題があります。JSONデータと一致しますが、一部の非JSONデータも一致します。たとえば、単一のリテラルはfalse一致しますが、最上位のJSON値は配列またはオブジェクトである必要があります。文字列またはスペースで使用できる文字セットにも多くの問題があります。
ドルメン2013年

31

はい、正規表現は正規言語にのみ一致するというのはよくある誤解です。実際、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 を使用する場合(検証済みの場合)、これが最善の方法です。


1
使用\dは危険です。多くの正規表現の実装\dでは、数字のUnicode定義と一致するだけ[0-9]でなく、代替スクリプトが含まれています。
ドルメン2013年

@dolmen:あなたは正しいかもしれませんが、質問に自分で編集するべきではありません。コメントとして追加するだけで十分です。
Dennis Haarbrink 2013年

\dPHPのPCREの実装では、Unicode番号と一致しないと思います。たとえば、٩記号(0x669アラビア語の数字9)はパターンを使用して一致します#\p{Nd}#uが、一致しません#\d#u
Hrant Khachatrian

@ hrant-khachatrian:/uフラグを使用しなかったためではありません。JSONはUTF-8でエンコードされています。適切な正規表現を使用するには、そのフラグを使用する必要があります。
ドルメン2013年

1
@dolmen私はu修飾子を使用しました。以前のコメントのパターンをもう一度見てください:)文字列、数値、およびブール値は、トップレベルで正しく一致しています。ここに長い正規表現をquanetic.com/Regexに貼り付けて、自分で試すことができます
Hrant Khachatrian

14

JSONの再帰的な性質(ネストされた{...}-s)のため、正規表現はそれを検証するのには適していません。確かに、一部の正規表現フレーバーはパターン*を再帰的に照合できます(そのため、JSONを照合できます)が、結果のパターンは見づらく、プロダクションコードIMOで使用することはできません。

*ただし、多くの正規表現実装は再帰パターンをサポートしていません。一般的なプログラミング言語のうち、Perl、.NET、PHP、Ruby 1.9.2は再帰的なパターンをサポートしています。



16
@すべての有権者:「正規表現は検証に適していない」とは、特定の正規表現エンジンがそれを実行できないことを意味するものではありません(少なくとも、それは私が意図したことです)。確かに、一部の正規表現の実装ではできますが、正しい考えの人なら誰でも単純にJSONパーサーを使用します。ハンマーだけで完全な家を建てる方法を誰かが尋ねるように、ハンマーは仕事に適していないと私は答えます、あなたは完全なツールキットと機械が必要でしょう。確かに、十分な持久力を持つ人はハンマーだけでそれを行うことができます。
Bart Kiers

1
これは有効な警告かもしれませんが、質問の答えにはなりません。正規表現は適切なツールではないかもしれませんが、選択肢がない人もいます。私たちは、サービスの出力を評価してそのヘルスをチェックするベンダー製品に縛られています。ベンダーがカスタムヘルスチェックに提供する唯一のオプションは、正規表現を受け入れるWebフォームです。サービスステータスを評価するベンダー製品は、私のチームの管理下にありません。私たちにとって、正規表現を使用したJSONの評価は要件となっているため、「不適切」という答えはありません。(私はまだあなたに反対票を投じませんでした。)
John Deters

11

@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オブジェクト/配列ではないJSON値(文字列、ブール値、数値)にも一致します。
kowsikbabu

4

JSONのドキュメントを見ると、目的が適合性を確認することだけである場合、正規表現は3つの部分である可能性があります。

  1. 文字列は[]またはで開始および終了します{}
    • [{\[]{1}...[}\]]{1}
  2. そして
    1. 文字は許可されたJSON制御文字(1つだけ)です
      • ... [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]...
    2. またはに含まれる文字のセット""
      • ... ".*?"...

すべて一緒に: [{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}

JSON文字列に文字が含まれている場合は、と一致するように正規表現フレーバーのスイッチをnewline使用する必要singlelineがあります。これはすべての不良JSONで失敗するわけではありませんが、基本的なJSON構造が無効な場合は失敗します。これは、パーサーに渡す前に基本的な妥当性検証を行う簡単な方法です。.newline


1
提案された正規表現は、特定のテストケースでひどいバックトラック動作をします。'{"a":false、 "b":true、 "c":100、 "'で実行しようとすると、この不完全なjsonは停止します。例:regex101.com/r/Zzc6sz。簡単な修正は:[{[] {1}([、:{} [] 0-9。\-+ Eaeflnr-u \ n \ r \ t] | "。*?")+ [}]] {1}
Toonijn

@Toonijnコメントを反映するために更新しました。ありがとう!
cjbarth 2017

3

私はマリオのソリューションの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

\ dの使用は危険です。多くの正規表現の実装では、\ dは[0-9]だけでなく代替スクリプトを含む数字のUnicode定義に一致します。したがって、RubyのUnicodeサポートがまだ壊れていない限り、コードの正規表現を修正する必要があります。
ドルメン2013年

私が知る限り、Rubyは\ dが「数字」のすべてのUnicode定義と一致しないPCREを使用しています。それともそれをすべきだと言っていますか?
pmarreck 2015

ないことを除いて。誤検知:「\ x00」、[真]。偽陰性:「\ u0000」、「\ n」。ぶら下がり: "[{" ":[{" ":[{" ":"(繰り返し1000x)。
2016

テストケースとして追加して、合格するコードを微調整するのはそれほど難しくありません。深さ1000以上のスタックを爆破しないようにする方法はまったく異なりますが、
pmarreck

1

「文字列と数字」については、数字の部分的な正規表現だと思います。

-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?

代わりに:

-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?

数値の小数部はオプションであり、また、大括弧の間に特別な意味があるため、-記号をエスケープする方がおそらく安全です。[+-]


使用\dは危険です。多くの正規表現の実装\dでは、数字のUnicode定義と一致するだけ[0-9]でなく、代替スクリプトが含まれています。
ドルメン2013年

-0は有効な数値ですが、RFC 4627で許可されており、正規表現はそれに準拠しています。
2013年

1

JSON配列の末尾のコンマが原因でPerl 5.16がハングしました。これはおそらくバックトラックを続けたためです。私はバックトラック終了ディレクティブを追加する必要がありました:

(?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
                                                                                   ^^^^^^^^

このように、「オプション」ではない構成(*または?)を識別した後は、それをバックトラックして、別の構成として識別しようとすべきではありません。


0

上記のように、使用する言語に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)

意図せずこれを壊すような何かを見逃した場合は、コメントをいただければ幸いです。


0

JSONArrayではなく単純な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)*\}$

このJSONで検証するサンプルデータ

{
"key":"string",
"key": 56,
"key":{
        "attr":"integer",
        "attr": 12
        },
"key":{
        "key":[
            {
                "attr": 4,
                "attr": "string"
            }
        ]
     }
}


-3

これは6年以上前のことだと思います。しかし、私はここで誰も言及していない解決策があると思います

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.