Я ищу регулярное выражение, которое позволяет мне проверять json.
Я очень новичок в Regex, и я знаю достаточно, что синтаксический анализ с Regex плох, но может ли он использоваться для проверки?
Я ищу регулярное выражение, которое позволяет мне проверять json.
Я очень новичок в 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
';
Это хорошо работает в PHP с функциями PCRE. Должен работать без изменений в Perl; и, безусловно, может быть адаптирован для других языков. Также он успешно работает с тестовыми примерами JSON.
Более простой подход - это минимальная проверка согласованности, как указано в RFC4627, раздел 6. Однако он предназначен только для проверки безопасности и основной меры предосторожности:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
Да, это распространенное заблуждение, что регулярные выражения могут соответствовать только обычным языкам. Фактически, функции 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 (если он проверен), то это лучший метод.
Из-за рекурсивного характера JSON (вложенных {...}
-s) регулярное выражение не подходит для его проверки. Конечно, некоторые ароматы регулярных выражений могут рекурсивно сочетаться с шаблонами * (и могут поэтому соответствовать JSON), но полученные шаблоны ужасны для просмотра и никогда не должны использоваться в производственном коде IMO!
* Остерегайтесь, однако, много реализаций регулярных выражений не поддерживают рекурсивные шаблоны. Из популярных языков программирования они поддерживают рекурсивные шаблоны: Perl,.NET, PHP и Ruby 1.9.2
Я попробовал ответить @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.
Я создал 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
Для "строк и чисел", я думаю, что частичное регулярное выражение для чисел:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
должен быть:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
так как десятичная часть числа является необязательной, а также, вероятно, безопаснее избегать символа -
в [+-]
, поскольку он имеет особое значение между скобками
Задняя запятая в массиве JSON заставляла мой Perl 5.16 зависать, возможно, потому, что он продолжал обратный отсчет. Я должен был добавить директиву с завершающим возвратом:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Таким образом, как только он идентифицирует конструкцию, которая не является "необязательной" (*
или ?
), она не должна пытаться отступать от нее, чтобы попытаться идентифицировать ее как нечто другое.
Как было написано выше, если язык, который вы используете, имеет 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)
Если бы я пропустил что-то, что бы разбить это непреднамеренно, я благодарен за комментарии!
Глядя на документацию на JSON, кажется, что регулярное выражение может просто быть тремя частями, если цель просто проверить пригодность:
[]
, либо {}
[{\[]{1}
... [}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Все вместе:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Если строка JSON содержит символы newline
, тогда вы должны использовать переключатель singleline
для вашего аромата регулярного выражения, чтобы .
соответствовал newline
. Обратите внимание, что это не сработает на всех плохих JSON, но это не удастся, если основная структура JSON недействительна, что является прямым способом сделать базовую проверку работоспособности перед передачей ее парсеру.
Здесь мое регулярное выражение для проверки строки:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Было написано usign исходная синтаксическая диаграмма.
Я понимаю, что это происходит более 6 лет назад. Тем не менее, я думаю, что есть решение, которое никто здесь не упомянул, что это намного проще, чем регулярное выражение
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}