Я шукаю регулярний вираз, який дозволяє мені перевірити 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 .
Більш простим підходом є мінімальна перевірка узгодженості, як зазначено у розділі 6 RFC4627 . Однак він просто призначений для перевірки безпеки та основного запобіжного заходу:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
falseзбігається, тоді як значення JSON верхнього рівня повинно бути або масивом, або об’єктом. Він також має багато проблем із набором символів, дозволеним у рядках або пробілах.
Так, поширена помилка, що регулярні вирази можуть відповідати лише звичайним мовам . Насправді функції 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 (якщо він перевірений), то це найкращий метод.
\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 є недійсною, що є прямим способом зробити базову перевірку надійності перед передачею її в парсер.
Я створив реалізацію 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+)?
оскільки десяткова частина числа необов’язкова, а також, можливо, безпечніше уникати -символу, [+-]оскільки він має спеціальне значення між дужками
\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}))*\"$
Була написана оригінальна синтаксична діаграма .