Я ищу Regex, который позволяет мне проверять json.
Я очень новичок в Regex, и я достаточно знаю, что синтаксический анализ с 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 + ')');
false
соответствует, тогда как значение JSON верхнего уровня должно быть либо массивом, либо объектом. У него также есть много проблем с набором символов, разрешенным в строках или в пробелах.
Да, это распространенное заблуждение, что регулярные выражения могут соответствовать только обычным языкам . Фактически, функции 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 (если он подтвержден), то это лучший метод.
\d
опасно. Во многих реализациях регулярных выражений \d
соответствует определению Unicode цифры, которое не просто, [0-9]
а вместо этого включает альтернативные сценарии.
\d
что не соответствует номерам Unicode в PHP-реализации PCRE. Например, ٩
символ (0x669 арабско-индийская цифра девять) будет сопоставлен с использованием шаблона, #\p{Nd}#u
но не будет#\d#u
/u
флаг. JSON кодируется в UTF-8. Для правильного регулярного выражения вы должны использовать этот флаг.
u
модификатор, пожалуйста, посмотрите еще раз на шаблоны из моего предыдущего комментария :) Строки, числа и логические значения правильно сопоставлены на верхнем уровне. Вы можете вставить длинное регулярное выражение сюда quanetic.com/Regex и попробовать себя
Из-за рекурсивного характера 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
это действительно правильно (согласно примечанию к руководству, и действительная строка 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 , кажется, что регулярное выражение может состоять просто из трех частей, если цель состоит только в проверке пригодности:
[]
либо{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Все вместе:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Если строка JSON содержит newline
символы, вам следует использовать singleline
переключатель в своем вкусе регулярного выражения, чтобы он .
соответствовал newline
. Обратите внимание, что это не приведет к ошибке для всех плохих JSON, но не удастся, если базовая структура JSON недействительна, что является прямым способом выполнить базовую проверку работоспособности перед передачей ее в синтаксический анализатор.
[{\[]{1}([,:{}\[\]0-9.\-+A-zr-u \n\r\t]|".*:?")+[}\]]{1}
Я создал 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+)?
поскольку десятичная часть числа является необязательной, и, вероятно, безопаснее избегать -
символа в, [+-]
поскольку он имеет особое значение между скобками
\d
опасно. Во многих реализациях регулярных выражений \d
соответствует определению Unicode цифры, которое не просто, [0-9]
а вместо этого включает альтернативные сценарии.
Завершающая запятая в массиве 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)
если я пропустил что-то, что могло бы случайно сломать это, я благодарен за комментарии!
он проверяет ключ (строка): значение (строка, целое число, [{ключ: значение}, {ключ: значение}], {ключ: значение})
^\{(\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)*\}$
{
"key":"string",
"key": 56,
"key":{
"attr":"integer",
"attr": 12
},
"key":{
"key":[
{
"attr": 4,
"attr": "string"
}
]
}
}
Вот мое регулярное выражение для проверки строки:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Написана с использованием оригинальной синтаксической схемы .
Я понимаю, что это было более 6 лет назад. Однако я думаю, что есть решение, о котором здесь никто не упоминал, которое намного проще, чем регулярное выражение
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}