Регулярний вираз для перевірки JSON


90

Я шукаю регулярний вираз, який дозволяє мені перевірити json.

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


31
Навіщо заморочуватися з окремим етапом перевірки? У більшості мов є JSON-бібліотеки, які можуть аналізувати JSON, і якщо він може аналізувати його, це було дійсним. Якщо ні, бібліотека скаже вам.
Епцилон,

Вам потрібно проаналізувати текст, щоб перевірити його ...
Кен

3
@mario - Я не знаю ... Я всі за зловживання регулярними виразами, і надзвичайно співчуваю вашому запереченню щодо "регулярного виразу", але це не стосується практичних питань, пов'язаних з роботою. Найкраща відповідь тут - це справді коментар Епцилона ... (можливо, ця дискусія належить у чаті?)
Кобі

1
Ще одним практичним випадком використання є пошук виразів JSON у більшому рядку. Якщо ви просто хочете запитати, "чи є цей рядок тут об'єктом JSON", тоді так, бібліотека синтаксичного аналізу JSON, мабуть, є кращим інструментом. Але він не може знайти об'єкти JSON у більшій структурі для вас.
Mark Amery

1
Це не відповідь, але ви можете використовувати цю частину бібліотеки JSON-js Крокфорда . Він використовує 4 регулярні вирази та розумно поєднує їх.
imgx64

Відповіді:


184

Так, можлива повна перевірка регулярних виразів.

Більшість сучасних реалізацій регулярних виразів допускають рекурсивні регулярні вирази, які можуть перевірити повну серіалізовану структуру 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 RFC4627 . Однак він просто призначений для перевірки безпеки та основного запобіжного заходу:

  var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
         text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
     eval('(' + text + ')');

22
+1 У світі стільки поганого від людей, які просто не отримують синтаксису регулярних виразів і неправильно використовують, що як причину їх ненавидіти :(
NikiC

8
@mario, не впевнений , якщо ви думаєте , що я в той-скептики-відділу , але не я. Зверніть увагу, що ваше твердження "Більшість сучасних реалізацій регулярних виразів дозволяють використовувати рекурсивні регулярні вирази" є дуже спірним. AFAIK лише Perl, PHP та .NET мають можливість визначати рекурсивні шаблони. Я б не називав це "самим".
Барт Кірс,

3
@ Барт: Так, це справедливо дискусійно. Найбільш іронічно, що механізми регулярних виразів Javascript не можуть використовувати такий рекурсивний регулярний вираз для перевірки JSON (або лише із складними обхідними шляхами). Отже, якщо регулярний вираз == posix регулярний вираз, це не варіант. Проте цікаво, що це можливо з сучасними реалізаціями; навіть з невеликою кількістю практичних випадків використання. (Але правда, libpcre не є поширеним двигуном скрізь.) - Також для протоколу: я сподівався на синтетичний значок розвороту, але ваше не отримання кількох прихильників перешкоджає цьому. : /
mario

4
Ні. Я був за популістським значком, за який мені потрібно 20 голосів, але все одно 10 голосів за вашу відповідь. Отож, навпаки, голоси проти вашого питання для цього мені не вигідні.
mario

2
Що ж, дивлячись далі, цей регулярний вираз має багато інших проблем. Він відповідає даним JSON, але деякі дані, що не належать до JSON, теж збігаються. Наприклад, один літерал falseзбігається, тоді як значення JSON верхнього рівня повинно бути або масивом, або об’єктом. Він також має багато проблем із набором символів, дозволеним у рядках або пробілах.
долмен

32

Так, поширена помилка, що регулярні вирази можуть відповідати лише звичайним мовам . Насправді функції 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 (якщо він перевірений), то це найкращий метод.


1
Використання \dнебезпечно. У багатьох реалізаціях регулярних виразів \dвідповідає визначенню Unicode цифри, яка є не простою, [0-9]а натомість включає альтернативні сценарії.
долмен

@dolmen: ти можеш мати рацію, але ти не повинен редагувати це сам у питанні. Достатньо просто додати його як коментар.
Dennis Haarbrink

Я думаю, \dщо не відповідає номерам Unicode у PHP-реалізації PCRE. Наприклад, ٩символ (0x669 цифра дев'ять з арабською мовою) буде співставлятись за шаблоном, #\p{Nd}#uале ні#\d#u
Грант Хачатрян

@ hrant-hachatrian: це не тому, що ви не використовували /uпрапор. JSON кодується в UTF-8. Для належного регулярного виразу слід використовувати цей прапор.
долмен

1
@dolmen Я використовував uмодифікатор, будь ласка, подивіться ще раз на шаблони у моєму попередньому коментарі :) Рядки, числа та логічні значення правильно співпадають на верхньому рівні. Ви можете вставити довгий регулярний вираз сюди quanetic.com/Regex і спробувати себе
Грант Хачатрян

14

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

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



16
@ всі голоси виборців: "регулярний вираз не підходить для його перевірки" не означає, що певні двигуни регулярних виразів не можуть цього зробити (принаймні, саме це я мав на увазі). Звичайно, деякі реалізації регулярних виразів можуть , але хтось здоровий розум просто скористається парсером JSON. Подібно до того, якби хтось запитав, як побудувати цілий будинок лише молотком, я відповів би, що молоток не підходить для роботи, вам знадобиться повний набір інструментів та обладнання. Звичайно, хтось із достатньою витривалістю може це зробити лише молотком.
Барт Кіерс,

1
Це може бути дійсним попередженням, але воно не відповідає на запитання . Regex може бути не правильним інструментом, але у деяких людей немає вибору. Ми заблоковані для продукту постачальника, який оцінює результати роботи служби, щоб перевірити її працездатність, і єдиним варіантом, який постачальник пропонує для власної перевірки стану, є веб-форма, яка приймає регулярний вираз. Товар постачальника, який оцінює статус послуги, не підконтрольний моїй команді. Для нас оцінка 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 є недійсною, що є прямим способом зробити базову перевірку надійності перед передачею її в парсер.


1
Запропонований регулярний вираз має жахливу поведінку відстеження у деяких тестових випадках. Якщо ви спробуєте запустити його на '{"a": false, "b": true, "c": 100, "' цей неповний json, він зупиняється. Приклад: regex101.com/r/Zzc6sz . Просте виправлення буде : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn

@Toonijn Я оновив, щоб відобразити ваш коментар. Дякую!
cjbarth

3

Я створив реалізацію 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

Використання \ d небезпечно. У багатьох реалізаціях регулярних виразів \ d відповідає визначенню Unicode цифри, яка не просто [0-9], а натомість включає альтернативні сценарії. Отже, якщо підтримка Unicode в Ruby все ще не порушена, вам доведеться виправити регулярний вираз у коді.
долмен

Наскільки мені відомо, Ruby використовує PCRE, в якому \ d не відповідає УСІМ визначенням Юнікоду "цифра". Або ти хочеш сказати, що так?
pmarreck

Хіба що ні. Помилково позитивний: "\ x00", [True]. Помилково негативний: "\ u0000", "\ n". Зависає на: "[{" ": [{" ": [{" ":" (повторюється 1000x).
nst

Не надто складно додати як тестові кейси, а потім налаштувати код для передачі. Як змусити його не підірвати стек глибиною 1000+ - це зовсім інша справа, проте ...
pmarreck

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* )
                                                                                   ^^^^^^^^

Таким чином, як тільки він ідентифікує конструкцію, яка не є "необов'язковою" ( *або ?), вона не повинна намагатися повернутися до неї, щоб спробувати ідентифікувати її як щось інше.


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"
            }
        ]
     }
}


-3

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

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.