web-dev-qa-db-ja.com

JSONを検証する正規表現

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

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

79
Shard

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

最新の正規表現の実装では、完全なJSONシリアル化構造を検証できる再帰的正規表現が許可されています。 json.org specification により、非常に簡単になります。

$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 with PCRE functions で非常にうまく動作します。Perlでは変更せずに動作するはずです;そして、他の言語に確実に適合させることができます。また、 JSONテストケース

よりシンプルなRFC4627検証

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

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

はい、正規表現は 正規言語 のみに一致するという誤解がよくあります。実際、PCRE関数は通常の言語よりもはるかに多く一致します。一部の非コンテキストフリー言語でも一致します。 WikipediaのRegExpsに関する記事 には特別なセクションがあります。

JSONは、いくつかの方法でPCREを使用して認識できます!@marioは、名前付きサブパターンと back-references を使用した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)_を使用しています。後者はentireパターンを参照していますが、_\A_がありますおよび_\Z_シーケンスは、サブパターン内で使用しないでください。 _(?1)_は、最も外側の括弧でマークされた正規表現への参照です(これが、最も外側の_( )_が_?:_で始まっていない理由です)。そのため、RegExpの長さは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
_

とにかく、これは実用的な解決策としてではなく、「技術実証」として扱われるべきです。 In PHP json_decode()関数を呼び出すことでJSON文字列を検証します(@Epcylonのように))。私がuseそのJSON(検証されている場合)、これが最良の方法です。

27

JSON(再帰{...}- s)、正規表現は検証に適していません。確かに、いくつかの正規表現フレーバーは再帰的にパターンを一致させることができます* (そのためJSONに一致する可能性があります)、しかし、結果のパターンは見るのが恐ろしく、本番コードIMOで決して使用されるべきではありません!

* ただし、多くの正規表現の実装ではnotが再帰的なパターンをサポートしています。人気のあるプログラミング言語のうち、これらは再帰的なパターンをサポートしています。Perl、.NET、PHP and Ruby 1.9.2

13
Bart Kiers

@marioの答えを試しましたが、JSON.orgからテストスイートをダウンロードし( archive )、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"]

両方に無効な文字が含まれています。そこで、次のようにパターンを更新しました(stringサブパターンが更新されました):

$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 のすべての法的テストに合格できます。

8
Gino Pane

私は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
3
pmarreck

[〜#〜] json [〜#〜] のドキュメントを見ると、目標がフィットネスをチェックするだけの場合、正規表現は単純に3つの部分になり得るようです。

  1. 文字列はandで始まり、[]または{} で終わります。
    • [{\[]{1} ...[}\]]{1}
  2. and
    1. 文字は許可されたJSON制御文字(1つのみ)です。
      • ...[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t] ...
    2. or"" に含まれる文字のセット
      • ...".*?" ...

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

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

3
cjbarth

JSON配列の末尾のコンマが原因で、Perl 5.16がハングアップしました。バックトラック終了ディレクティブを追加する必要がありました。

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

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

1
user117529

「文字列と数字」の場合、数字の部分的な正規表現は次のようになります。

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

代わりに:

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

数の小数部分はオプションであり、-内の[+-]記号は角括弧の間に特別な意味があるため、エスケープする方が安全です。

1
Mikaeru

上記で書いたように、使用する言語に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
exside