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

91

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

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

Осколок
источник
31
Зачем нужен отдельный этап проверки? В большинстве языков есть JSON-библиотеки, которые могут анализировать JSON, и если он может его анализировать, это было правильно. Если нет, библиотека сообщит вам.
Epcylon
Вам нужно проанализировать текст, чтобы проверить его ...
Кен
3
@mario - Я не знаю ... Я полностью за злоупотребление регулярным выражением и очень сочувствую вашему возражению против заблуждения "регулярное выражение должно соответствовать обычному" - но не по практическим вопросам, связанным с работой. Лучший ответ здесь - действительно комментарий Epcylon ... (может быть, это обсуждение относится к чату?)
Коби
1
Другой практический вариант использования - поиск выражений JSON в более крупной строке. Если вы просто хотите спросить, «является ли эта строка здесь объектом JSON?», Тогда да, библиотека синтаксического анализа JSON, вероятно, лучший инструмент. Но он не может найти для вас объекты JSON в более крупной структуре.
Марк Эмери
1
Это не ответ, но вы можете использовать эту часть библиотеки Crockford JSON-js . Он использует 4 регулярных выражения и умело их объединяет.
imgx64

Ответы:

187

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

Большинство современных реализаций регулярных выражений допускают рекурсивные повторные выражения, которые могут проверять полную сериализованную структуру 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 + ')');
Марио
источник
23
+1 В мире столько всего плохого от людей, которые просто не понимают синтаксис регулярных выражений и злоупотребляют этим как причиной их ненависти :(
NikiC 05
8
@mario, не уверен, что я из отдела скептиков , но это не так. Обратите внимание, что ваше утверждение «Большинство современных реализаций регулярных выражений допускают рекурсивные повторные выражения» является весьма спорным. AFAIK, только Perl, PHP и .NET имеют возможность определять рекурсивные шаблоны. Я бы не назвал это «самым».
Bart Kiers
3
@Bart: Да, это правильно спорно. По иронии судьбы, механизмы регулярных выражений Javascript не могут использовать такое рекурсивное регулярное выражение для проверки JSON (или только с помощью сложных обходных путей). Так что если regex == posix regex, это не вариант. Тем не менее интересно, что это выполнимо с помощью современных реализаций; даже с несколькими практическими вариантами использования. (Но правда, libpcre не является распространенным движком повсюду.) - Также для протокола: я надеялся на значок синтетического разворота, но то, что вы не получили несколько одобрительных голосов, мешает этому. : /
марио
4
Нет. Я был за значком «Популист», за который мне нужно 20 голосов, но все же 10 голосов за ваш ответ. Напротив, отрицательные голоса по вашему вопросу не в мою пользу.
Марио
2
Что ж, если заглянуть дальше, у этого регулярного выражения есть много других проблем. Он соответствует данным JSON, но некоторые данные, не относящиеся к JSON, также совпадают. Например, один литерал falseсоответствует, тогда как значение JSON верхнего уровня должно быть либо массивом, либо объектом. У него также есть много проблем с набором символов, разрешенным в строках или в пробелах.
дольмен
32

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

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 (если он подтвержден), то это лучший метод.

Грант Хачатрян
источник
1
Использование \dопасно. Во многих реализациях регулярных выражений \dсоответствует определению Unicode цифры, которое не просто, [0-9]а вместо этого включает альтернативные сценарии.
дольмен
@dolmen: возможно, вы правы, но вы не должны редактировать это самостоятельно в вопросе. Достаточно просто добавить его в качестве комментария.
Деннис Хаарбринк
Я думаю, \dчто не соответствует номерам Unicode в PHP-реализации PCRE. Например, ٩символ (0x669 арабско-индийская цифра девять) будет сопоставлен с использованием шаблона, #\p{Nd}#uно не будет#\d#u
Грант Хачатрян
@ Грант-Хачатрян: нет, потому что вы не использовали /uфлаг. JSON кодируется в UTF-8. Для правильного регулярного выражения вы должны использовать этот флаг.
дольмен
1
@dolmen Я использовал uмодификатор, пожалуйста, посмотрите еще раз на шаблоны из моего предыдущего комментария :) Строки, числа и логические значения правильно сопоставлены на верхнем уровне. Вы можете вставить длинное регулярное выражение сюда quanetic.com/Regex и попробовать себя
Грант Хачатрян
14

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

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

Барт Кирс
источник
16
@ всех избирателей: «регулярное выражение не подходит для его проверки» не означает, что определенные механизмы регулярных выражений не могут этого сделать (по крайней мере, это то, что я имел в виду). Конечно, некоторые реализации регулярных выражений могут , но любой в здравом уме просто использовал бы парсер JSON. Точно так же, как если кто-то спросит, как построить полноценный дом, используя только молоток, я отвечу, что молоток не подходит для этой работы, вам понадобится полный набор инструментов и оборудование. Конечно, кто-то с достаточной выносливостью может сделать это одним лишь молотком.
Bart Kiers
1
Это может быть правильным предупреждением, но оно не отвечает на вопрос . Регулярное выражение может быть неправильным инструментом, но у некоторых людей нет выбора. Мы привязаны к продукту поставщика, который оценивает выходные данные службы для проверки ее работоспособности, и единственный вариант, который поставщик предоставляет для настраиваемой проверки работоспособности, - это веб-форма, которая принимает регулярное выражение. Продукт поставщика, который оценивает статус обслуживания, не находится под контролем моей команды. Для нас оценка JSON с помощью регулярного выражения теперь является требованием, поэтому ответ «неподходящий» не является жизнеспособным. (Я все еще не голосовал против вас.)
Джон Детерс
12

Я попробовал ответить @ 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 .

Джино Пейн
источник
Это также будет соответствовать только значениям JSON (строки, логические значения и числа), которые не являются объектом / массивом JSON.
kowsikbabu
4

Глядя на документацию для 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 недействительна, что является прямым способом выполнить базовую проверку работоспособности перед передачей ее в синтаксический анализатор.

Cjbarth
источник
1
Предлагаемое регулярное выражение имеет ужасное поведение при возврате в некоторых тестовых случаях. Если вы попробуете запустить его на '{"a": false, "b": true, "c": 100, "', этот неполный json, он остановится. Пример: regex101.com/r/Zzc6sz . Простое исправление: : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn
@Toonijn Я обновил, чтобы отразить ваш комментарий. Благодаря!
cjbarth 03
Эта слегка измененная версия @cjbarth идеально подходит для моего случая использования поиска всех JSON-подобных структур в тексте (глобально применяется к HTML-файлу в моем случае):[{\[]{1}([,:{}\[\]0-9.\-+A-zr-u \n\r\t]|".*:?")+[}\]]{1}
C2BB
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
Pmarreck
источник
Использование \ d опасно. Во многих реализациях регулярных выражений \ d соответствует определению Unicode цифры, которое не просто [0-9], но вместо этого включает альтернативные сценарии. Поэтому, если поддержка Unicode в Ruby по-прежнему не работает, вам нужно исправить регулярное выражение в своем коде.
дольмен
Насколько мне известно, Ruby использует PCRE, в котором \ d не соответствует ВСЕМ определениям Unicode для "digit". Или вы говорите, что надо?
pmarreck
За исключением того, что это не так. Ложно-положительный результат: «\ x00», [Верно]. Ложноотрицательный: «\ u0000», «\ n». Зависает: "[{" ": [{" ": [{" ":" (повторяется 1000 раз).
nst
Нетрудно добавить в качестве тестовых примеров, а затем настроить код для прохождения. Как заставить его не взорвать стек с глубиной 1000+ - это совсем другое дело ...
pmarreck 08
1

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

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

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

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

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

Микаэру
источник
Использование \dопасно. Во многих реализациях регулярных выражений \dсоответствует определению Unicode цифры, которое не просто, [0-9]а вместо этого включает альтернативные сценарии.
дольмен
Выглядит немного странно, что -0 - допустимое число, но RFC 4627 разрешает это, и ваше регулярное выражение ему соответствует.
откр
1

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

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

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

user117529
источник
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

Регулярное выражение, которое проверяет простой JSON, а не JSONArray

он проверяет ключ (строка): значение (строка, целое число, [{ключ: значение}, {ключ: значение}], {ключ: значение})

^\{(\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"
            }
        ]
     }
}
Рави Нандасана
источник
-2

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

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
Джейми
источник