Écrire un validateur JSON

12

Écrivez un programme qui détermine si son entrée est JSON valide .

  • Entrée: texte ASCII:[\x00-\x7F]*

    Remarque: si ASCII est problématique, n'hésitez pas à utiliser un autre encodage, mais indiquez-le dans votre message.

  • Sortie: Valid ou Invalid. Le retour à la ligne peut être omis.

  • Exemple:

    $ echo '{"key": "value"}' | ./json-validate
    Valid
    $ echo '{key: "value"}' | ./json-validate
    Invalid
    
  • Règles:

    • N'utilisez pas de bibliothèque d'analyse JSON.
    • Des solutions partiellement correctes sont autorisées, mais désapprouvées.
    • Publiez votre score de suite de tests (voir ci-dessous).

La solution correcte la plus courte l' emporte.

Veuillez exécuter json-validate-test-suite.sh sur votre programme et publier votre score. Exemple:

$ ./json-validate-test-suite.sh ./buggy-prog
fail: should be invalid:  [ 0.1e ] 
fail: should be invalid:  [ 0.1e+-1 ] 
fail: should be invalid:  [ 0.1e-+1 ] 
score: 297/300

Ressources:

  • json.org - Définition concise de la grammaire JSON avec des images faciles à suivre.
  • RFC 4627 - Spécification JSON
  • json-validate.c - Une implémentation de 200 lignes qui réussit la suite de tests.

La grammaire JSON est la suivante:

json: object | array

object: '{' members? '}'
    members: pair (',' pair)*
    pair:    string ':' value

array: '[' elements? ']'
    elements: value (',' value)*

value: string
     | number
     | object
     | array
     | 'true'
     | 'false'
     | 'null'

string: '"' char* '"'
    char: [^"\\\x00-\x1F]
        | '\' escape
    escape: ["\\/bfnrt]
          | u [0-9A-Fa-f]{4}

number: '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)?

En outre, un espace peut apparaître avant ou après l'un des six caractères structurels {}[]:,

ws = [\t\n\r ]*

Gardez à l'esprit les points suivants:

  • Soyez prudent avec des fonctions comme isspace(). L'espace dans JSON est [\t\n\r ], mais isspace()traite également \v(onglet vertical) et \f(flux de formulaire) comme un espace. Bien que le mot isdigit()puisse accepter plus que juste[0-9] , il devrait être acceptable de l'utiliser ici, car nous supposons que l'entrée est en ASCII.
  • \x7Fest techniquement un caractère de contrôle, mais le RFC JSON ne le mentionne pas (il mentionne seulement [\x00-\x1F]), et la plupart des analyseurs JSON ont tendance à accepter les \x7Fcaractères dans les chaînes. En raison de cette ambiguïté, les solutions peuvent choisir de les accepter ou non.
Joey Adams
la source
7
Votre note "solutions partiellement correctes permises" me fait rêver d'extraire une expression régulière d'un algorithme génétique. Je dois être fou.
JB
@JB: Ce serait génial.
Joey Adams
Juste curieux, pourquoi {key: "value"}considérer JSON invalide? C'est du javascript valide.
HoLyVieR
@HoLyVieR: J'imagine que c'est pour que JSON soit plus facile à analyser et moins ambigu pour les implémenteurs. Je ne suis pas sûr non plus que j'aime cette restriction.
Joey Adams
Quelqu'un a obtenu une copie du script de validation?
Armand

Réponses:

7

PHP: 297 285 264 253 caractères

<?=preg_match(<<<'R'
~([\h
]*)({(?1)((("([^"\\\0- ]| |\\(["\\/bfnrt]|u[\dA-Fa-f]{4}))*")(?1):(?1)((?5)|-?(0|[1-9]\d*)(\.\d+)?([Ee][+-]?\d+)?|(?2)|true|false|null))(((?1),(?1))(?4))*)?}|\[(?1)((?8)((?13)(?8))*)?(?1)])(?1)\z~A
R
,`cat`)?'Valid':'Invalid';

score: 300/300

Il s'agit d'une implémentation complète et récursive de la grammaire JSON.

Il ne fonctionne que sur PHP ≥ 5.3 en raison de Nowdoc syntaxe (heredoc aurait fallu doubler tous \).

Version lisible:

(c'est le même regex, avec des groupes de capture nommés et une syntaxe étendue):

#!/usr/bin/env php
<?php

$re = <<< 'RE'
~\A (?P<ws>[\t\n\r ])* (
    (?P<object>\{ (?P>ws)*
        (?P<members>
            (?P<pair>
                (?P<string>
                    "(?P<char>
                        [^"\\\x00-\x1F]
                        |\\(?P<escape>
                            ["\\/bfnrt]
                            |u [0-9A-Fa-f]{4}
                        )
                    )*"
                ) (?P>ws)* : (?P>ws)* (?P<value>
                    (?P>string)
                    | (?P<number>-? (0 | [1-9][0-9]*) (\. [0-9]+)? ([Ee] [+-]? [0-9]+)? )
                    | (?P>object)
                    | (?P>array)
                    | true
                    | false
                    | null
                )
            ) ( (?P>ws)* , (?P>ws)* (?P>pair) )*
        )?
    \})
    |(?P<array>\[ (?P>ws)*
        (?P<elements>
            (?P>value) ( (?P>ws)* , (?P>ws)* (?P>value) )*
        )?
    (?P>ws)* \])
) (?P>ws)* \z~x
RE;

if (preg_match($re, stream_get_contents(STDIN))) {
    echo 'Valid';
} else {
    echo 'Invalid';
}
Arnaud Le Blanc
la source
Sensationnel. `` `` ``
Nathan Osman
Vous devez inclure l' <?phpOMI.
lâche anonyme le
Ajoutée. 264 caractères maintenant :-)
Arnaud Le Blanc
5

Python - 340 314 299 292 caractères

import re,os
r=x=re.sub
z=r('0\.0+','0',r('e[+-]?0+|[\t\n\r]',' ',r(r'"(\\["nrtb\\/]|[^\\"\0-\37])*"','1',r(r'true|false|null|\\u\w{4}|[1-9]\d*','0',os.read(0,99)))))
while z!=x:z,x=r('\{(1:\d)?(,\\1)*\}|\[(-?\d(,-?\d)*)?\]','0',r(' *([][{}:,]) *','\\1',z)),z
print['Inv','V'][z=='0']+'alid'

But

$ ./json-validate-test-suite.sh ./codegolf-474.py
score: 300/300
VOUS
la source
3

Scala - 390 caractères

import scala.util.parsing.combinator.JavaTokenParsers
object J extends JavaTokenParsers{def j=o|a
def o:Parser[Any]="{"~repsep(p,",")~"}"
def p=s~":"~v
def a:Parser[Any]="["~repsep(v,",")~"]"
def v=s|o|a|"true"|"false"|"null"
def s=stringLiteral
def n=floatingPointNumber}
object Main{def main(a:Array[String]){print(if(J.parseAll(J.j,readLine()).successful)"Valid"else"Invalid")}}

Il s'agit d'un soluton simple, utilisant des combinateurs d'analyseurs. Écrit en 1 ou 2 minutes, littéralement. Impossible d'obtenir le script de validation, le navigateur a indiqué que le serveur était introuvable.

Afficher un nom
la source
ressemble à une solution intéressante; le lien du validateur a été corrigé.
Armand
Existe-t-il un moyen simple de le faire sous Windows? (sans cygwin ou hérésie similaire)
Nom d'affichage