[regex] JSON의 유효성을 검사하는 정규식

json의 유효성을 검사 할 수있는 Regex를 찾고 있습니다.

저는 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   
';

그것은 PCRE 기능을 가진 PHP에서 아주 잘 작동 합니다 . Perl에서 수정하지 않고 작동해야합니다. 다른 언어에도 적용 할 수 있습니다. 또한 JSON 테스트 케이스 에서도 성공합니다 .

더 간단한 RFC4627 검증

더 간단한 접근 방식은 RFC4627, 섹션 6에 지정된 최소 일관성 검사 입니다. 그러나 이는 보안 테스트 및 기본 비유 효성 예방 조치 일뿐입니다.

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


답변

예, 정규 표현식이 정규 언어 와 만 일치 할 수 있다는 것은 일반적인 오해입니다 . 실제로 PCRE 함수는 일반 언어보다 훨씬 더 많이 일치 할 수 있으며 컨텍스트가없는 일부 언어 와도 일치 할 수 있습니다! RegExps 에 대한 Wikipedia의 기사 에는 그것에 대한 특별한 섹션이 있습니다.

JSON은 PCRE를 사용하여 여러 가지 방법으로 인식 할 수 있습니다! @mario는 명명 된 하위 패턴과 역 참조를 사용하는 하나의 훌륭한 솔루션을 보여주었습니다 . 그런 다음 그는 재귀 패턴을 사용하는 솔루션이 있어야한다고 지적했습니다 (?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)가장 바깥 쪽 괄호로 표시된 정규 표현식에 대한 참조 (이것이 가장 바깥 쪽 ( )이로 시작하지 않는 이유 입니다 ?:). 따라서 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

어쨌든 이것은 실질적인 해결책이 아니라 “기술 데모”로 취급되어야합니다. PHP에서 json_decode()함수 를 호출하여 JSON 문자열의 유효성을 검사합니다 (@Epcylon이 언급 한 것처럼). 해당 JSON 을 사용 하려는 경우 (검증 된 경우) 이것이 가장 좋은 방법입니다.


답변

JSON (중첩 된 {...}-s) 의 재귀 적 특성으로 인해 정규식은 유효성을 검사하는 데 적합하지 않습니다. 물론 일부 정규식 버전은 재귀 적으로 패턴 과 일치 할 수 있지만 * (그리고이를 위해 JSON과 일치시킬 수 있음) 결과 패턴은보기에 끔찍하며 프로덕션 코드 IMO에서 절대 사용해서는 안됩니다!

* 그러나 많은 정규식 구현은 재귀 패턴을 지원 하지 않습니다 . 널리 사용되는 프로그래밍 언어 중 Perl, .NET, PHP 및 Ruby 1.9.2와 같은 재귀 패턴을 지원합니다.


답변

@mario의 대답을 시도했지만 JSON.org ( archive ) 에서 테스트 스위트를 다운로드 했고 4 개의 실패한 테스트 (fail1.json, fail18.json, fail25.json, fail27) 가 있었기 때문에 작동하지 않았습니다 . json).

나는 오류를 조사하고, 발견 한 fail1.json(수동의에 따라 실제로 올 노트RFC-7159 유효한 문자열도 유효한 JSON)입니다. 파일도 사실 fail18.json이 아니 었습니다. 실제로 깊이 중첩 된 JSON이 실제로 포함되어 있기 때문입니다.

[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]

그래서 두 개의 파일 왼쪽 : fail25.jsonfail27.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 에 대한 문서를 살펴보면 목표가 적합성을 확인하는 것이라면 정규식이 단순히 세 부분이 될 수 있습니다.

  1. 문자열은 또는로 시작 하고 끝납니다.[]{}
    • [{\[]{1}[}\]]{1}
    1. 이 문자는 허용되는 JSON 제어 문자입니다 (하나만).
      • [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
    2. 또는 a에 포함 된 문자 집합""
      • ".*?"

모두 함께:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}

json으로 문자열이 포함되어 있으면 newline문자를, 당신은 사용해야 singleline있도록 정규식 맛에 스위치를 .일치 newline. 모든 잘못된 JSON에서 실패하지는 않지만 기본 JSON 구조가 유효하지 않으면 실패합니다. 이는 파서에 전달하기 전에 기본 온 전성 유효성 검사를 수행하는 간단한 방법입니다.


답변

작동하는 Mario 솔루션의 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+)?

숫자의 소수 부분은 선택 사항이며 대괄호 사이에 특별한 의미가 있으므로 -기호 를 이스케이프하는 것이 더 안전 할 수 [+-]있습니다.