Regex для проверки JSON - PullRequest
       17

Regex для проверки JSON

78 голосов
/ 06 апреля 2010

Я ищу Regex, который позволяет мне проверять JSON.

Я очень новичок в Regex и знаю, что синтаксический анализ с помощью Regex плох, но можно ли его использовать для проверки?

Ответы [ 11 ]

167 голосов
/ 02 октября 2010

Да, возможна полная проверка регулярных выражений.

Большинство современных реализаций регулярных выражений допускают рекурсивные регулярные выражения, которые могут проверить полную сериализованную структуру 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 + ')');
26 голосов
/ 06 июня 2011

Да, распространенным заблуждением является то, что регулярные выражения могут соответствовать только обычным языкам . Фактически, функции PCRE могут соответствовать гораздо большему, чем обычные языки , они могут соответствовать даже некоторым неконтекстно-свободным языкам! В статье Википедии о RegExps есть специальный раздел об этом.

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 с помощью вызова функции json_decode() (точно так же, как заметил @Epcylon). Если я собираюсь использовать этот JSON (если он проверен), то это лучший метод.

13 голосов
/ 06 апреля 2010

Из-за рекурсивной природы JSON (вложенный {...} -s) регулярное выражение не подходит для его проверки. Конечно, некоторые разновидности регулярных выражений могут рекурсивно соответствовать шаблонам * (и поэтому могут соответствовать JSON), но результирующие шаблоны ужасно смотреть и никогда не должны использоваться в производственном коде IMO!

* Но будьте осторожны, многие реализации регулярных выражений не поддерживают рекурсивные шаблоны. Из популярных языков программирования они поддерживают рекурсивные шаблоны: Perl, .NET, PHP и Ruby 1.9.2

7 голосов
/ 25 июля 2016

Я попробовал ответ @ mario, но он у меня не сработал, потому что я скачал набор тестов с JSON.org ( архив ) и было 4 неудачных теста (fail1.json, fail18 .json, fail25.json, fail27.json).

Я исследовал ошибки и обнаружил, что fail1.json на самом деле правильно (согласно примечанию и RFC-7159 действительной строки руководства также является допустимым JSON) Файл fail18.json также не соответствует действительности, потому что он содержит действительно правильный глубоко вложенный JSON:

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

Итак, осталось два файла: 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 могут быть пройдены.

3 голосов
/ 18 июля 2017

Глядя на документацию по JSON , кажется, что регулярное выражение может состоять из трех частей, если целью является просто проверка на пригодность:

  1. Строка начинается с , а заканчивается либо [], либо {}
    • [{\[]{1} ... [}\]]{1}
  2. и
    1. Символ является допустимым управляющим символом JSON (только один)
      • ... [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t] ...
    2. или Набор символов, содержащийся в ""
      • ... ".*?" ...

Все вместе: [{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}

Если строка JSON содержит newline символов, то вам следует использовать переключатель singleline в своем выражении регулярного выражения, чтобы . соответствовало newline. Обратите внимание, что это не сработает на всех плохих JSON, но сработает, если базовая структура JSON будет недопустимой, что является простым способом выполнить базовую проверку работоспособности перед передачей ее парсеру.

3 голосов
/ 24 мая 2012

Я создал Ruby-реализацию решения Mario, которая работает:

# 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
1 голос
/ 14 сентября 2012

Завершающая запятая в массиве JSON приводила к зависанию моего Perl 5.16, возможно, потому что он продолжал возвращаться. Мне пришлось добавить завершающую директиву возврата:

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

Таким образом, как только он идентифицирует конструкцию, которая не является «необязательной» (* или ?), он не должен пытаться вернуться назад, чтобы попытаться идентифицировать ее как что-то еще.

1 голос
/ 03 ноября 2010

Для «строк и чисел» я думаю, что частичное регулярное выражение для чисел:

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

должно быть вместо:

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

, поскольку десятичная часть числа является необязательной, а также, возможно, безопаснее экранировать символ - в [+-], поскольку он имеет особое значение в скобках

0 голосов
/ 28 июня 2016

Я понимаю, что это более 6 лет назад. Тем не менее, я думаю, что есть решение, которое здесь никто не упомянул, которое намного проще, чем регулярное выражение

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
0 голосов
/ 07 января 2015

Как было написано выше, если в используемом вами языке есть 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)

если я пропустил что-то, что непреднамеренно сломало бы это, я благодарен за комментарии!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...