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

Я ищу регулярное выражение, которое позволяет мне проверять json.

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

Ответ 1

(Это регулярное выражение было доведено до вас от депонирующего агента, не прошедшего проверку).

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

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

Ответ 2

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

JSON можно распознать с помощью PCRE несколькими способами! @mario продемонстрировал одно большое решение, используя именованные подшаблоны и back-references. Затем он отметил, что должно быть решение с использованием рекурсивных шаблонов (?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 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), потому что последний ссылается на шаблон whole, но мы имеем последовательности \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 (если он проверен), то это лучший метод.

Ответ 3

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

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

Ответ 4

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

Я исследовал ошибки и выяснил, что fail1.json на самом деле правильный (в соответствии с руководством note и 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.

Ответ 5

Я создал 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

Ответ 6

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

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

должен быть:

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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

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

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

Ответ 11

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

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}