Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

Parser un JSON


Sujet :

Langage PHP

  1. #1
    Nouveau Candidat au Club
    Parser un JSON
    Bonsoir à tous,
    J'ai une question très bateau, mais je préfère demander l'avis de gens qui maîtrisent le sujet avant de faire n'importe quoi.
    J'ai besoin de parser ce fichier JSON: http://drive.google.com/open?id=1hCa...rN8mqky71JLMJP

    On me demande
    1) de parser tous les identifiants uniques du type "SV350".

    Le regex que je suggère est: "[A-Z]{2}[1-9]{3}":{
    Qu'en pensez-vous s'il vous plaît ?

    2) de parser tous les identifiants uniques du type "SV350" en ajoutant la condition "land":"ESP".
    La solution que je propose est: (?(?="land":"ESP")"[A-Z]{2}[1-9]{3}":{)
    Qu'en pensez-vous s'il vous plaît ? Je ne parviens pas à comprendre comment faire en sorte que ce soit le bon identifiant qui soit sorti et pas l'identifiant suivant par exemple.

    Merci d'avance pour votre aide,
    Nicolas.

  2. #2
    Modérateur

    Tu compte pas parser le json comme une chaine de caractère a coup de regex quand même ?

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    $datas = json_decode($chainejson,true);

    Va te donner un tableau de toutes les données que tu peux ensuite facilement parcourir pour appliquer le traitement que tu veux.

    http://php.net/manual/fr/function.json-decode.php
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  3. #3
    Nouveau Candidat au Club
    Hello grunk,
    Merci pour ton retour.

    On me demande pas d'introduire du php dans cette histoire, à vrai dire on ne me demande pas le livrable final seulement le regex.
    Voilà les consignes exactes: https://drive.google.com/drive/folde...MHyB21lPgdudWl
    Il s'agit du 2.2

    Merci d'avance,
    Nicolas.

  4. #4
    Modératrice

    Citation Envoyé par Nicolas_LesPaul Voir le message

    On me demande
    1) de parser tous les identifiants uniques du type "SV350".

    Le regex que je suggère est: "[A-Z]{2}[1-9]{3}":{
    Qu'en pensez-vous s'il vous plaît ?
    Si aucune autre chaine n'a le même format dans ton json, ça devrait fonctionner. (sauf que les chiffres commencent à 0, pas à 1 )

    Citation Envoyé par Nicolas_LesPaul Voir le message

    2) de parser tous les identifiants uniques du type "SV350" en ajoutant la condition "land":"ESP".
    La solution que je propose est: (?(?="land":"ESP")"[A-Z]{2}[1-9]{3}":{)
    Qu'en pensez-vous s'il vous plaît ? Je ne parviens pas à comprendre comment faire en sorte que ce soit le bon identifiant qui soit sorti et pas l'identifiant suivant par exemple.
    Hum... là ça dépasse mes maigres compétences en regex.
    Modératrice PHP
    Aucun navigateur ne propose d'extension boule-de-cristal : postez votre code et vos messages d'erreurs. (Rappel : "ça ne marche pas" n'est pas un message d'erreur)
    Cherchez un peu avant poser votre question : Cours et Tutoriels PHP - FAQ PHP - PDO une soupe et au lit !.

    Affichez votre code en couleurs : [CODE=php][/CODE] (bouton # de l'éditeur) et [C=php][/C]

  5. #5
    Modérateur

    Pour la première OK , je supprimerais la partie ":{" pour pas être embété avec d’éventuels espace qui pourraient être (ou non) présents.

    Pour la seconde comme Celira , je sais pas faire.

    Ceci étant dit c'est stupide de faire ce genre d'exercice sur du json , qui est un formatage justement pensé pour ne pas avoir à faire ça puisque largement intégré dans plein de langage.

    Pour tes tests de regex , tu peux utiliser https://regex101.com/ , très pratique !
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  6. #6
    Nouveau Candidat au Club
    Bonjour à tous,
    Merci pour vos retours !

    Pour le 1) on est d'accord que les parenthèses sont facultatives ?

    Pour le 2) il s'agit d'une syntaxe conditionnelle respectant le pcre ?

    Merci encore,
    Nicolas.

  7. #7
    Nouveau Candidat au Club
    Merci pour ton retour, cela dit inutile de monter sur tes grands chevaux.
    Si tu ne souhaites pas me répondre pour une raison x ou y, éh bien ne me réponds pas...
    J'ai pris soin de lire ton message avec attention, je n'y ai pas trouvé d'explication justifiant la nécessaire utilisation de parenthèses.

    Tiens, intéresse-toi à la notion de nexus savoir-pouvoir théorisée par Michel Foucault et mets ça en parallèle avec l'enseignement socratique sur les liens entre le savoir, l'intelligence et l'humilité.

  8. #8
    Modératrice

    Pour autant que je sache, les parenthèses servent à capturer un groupe de façon pouvoir l'extraire ou le remplacer. Donc si le but est uniquement de vérifier si l'expression est valide, les parenthèses ne sont pas nécessaires.
    Modératrice PHP
    Aucun navigateur ne propose d'extension boule-de-cristal : postez votre code et vos messages d'erreurs. (Rappel : "ça ne marche pas" n'est pas un message d'erreur)
    Cherchez un peu avant poser votre question : Cours et Tutoriels PHP - FAQ PHP - PDO une soupe et au lit !.

    Affichez votre code en couleurs : [CODE=php][/CODE] (bouton # de l'éditeur) et [C=php][/C]

  9. ###raw>post.musername###
    Expert confirmé
    Je ne sais pas trop comment prendre "Please check it[']s structure first": S'agit-il de vérifier en premier lieu si la chaîne est bien du JSON? Dans ce cas, tu peux utiliser cette pattern:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $json = file_get_contents('sample.json');
    $pattern = <<<'EOD'
    ~
    (?(DEFINE)
        (?<number> -?(?:[1-9][0-9]*(?:\.[0-9]+)?|0(?:\.[0-9]+)?)(?:[eE][-+]?[0-9]+)? )
        (?<null> null )
        (?<boolean> true | false )
        (?<string> " [^"\\]*+ (?:\\.[^"\\]*)*+ " )
     
        (?<list> \[ \s* (?: \g<value> (?: \s* , \s* \g<value> )*+ )?+ \s* ] )
        (?<object> { \s* (?: \g<keyval> (?: \s* , \s* \g<keyval> )*+ )?+ \s* } )
     
        (?<value> \g<number> | \g<null> | \g<boolean> | \g<string> | \g<list> | \g<object> )
        (?<keyval> \g<string> \s* : \s* \g<value> )
    )
     
    \A \g<object> $
     
    ~x
    EOD;
    var_dump(preg_match($pattern, $json)); // int(1)

    Remarques à propos de ce code:
    • Cette pattern est en deux parties: la première partie (?(DEFINE) ... ) définie les différents éléments à l'aide de groupes nommés ainsi que leurs rapports entre eux: un groupe peut se définir par rapport à un autre groupe voire à lui même, ce qui éventuellement implique une structure récursive (comme l'est le JSON). Cette première partie sert juste à nommer les choses et à établir des rapports entre elles, mais concrètement c'est une sous-pattern vide, elle ne matche rien (zero-width assertion) et n'impose rien non plus comme le ferait un test avant (lookahead) ou arrière (lookbehind) ou encore une ancre (^, $, \A, \z).
      La seconde partie \A \g<object> $ est la pattern comme on l'entend communément, c-à-d la description de la chaîne en elle-même, et ce, même si elle fait référence aux éléments précédemment définis.
    • Comme pcre est un moteur à backtracking et afin de limiter celui-ci (le backtracking), nombre de quantificateurs sont passés en "possessif" (?+, *+, ++). Ceci dit, l'impact de cette mesure reste à nuancer puisque une des particularités de pcre (contrairement à Perl) est de rendre atomique tout appel à une sous-pattern. Concrètement, si je fais un appel à, disons, \g<number> et que celui-ci réussi, il ne sera plus possible d'effectuer un backtracking sur les caractères consommés par \g<number> si la sous pattern suivante échoue, sauf à éliminer l'appel en question d'un seul bloc.
    • J'utilise l'ancre $ au lieu de l'ancre \z car elle est plus permissive (celle-ci réussit dans deux cas: soit à la fin de la chaîne, soit juste avant le dernier caractère de la chaîne quand celui-ci est un \n, alors que \z matche strictement la fin de la chaîne). Or il se trouve que ton fichier json se termine par un \n juste après la dernière accolade fermante. On aurait aussi pu écrire \A \g<object> \n? \z ou encore \A \g<object> \s* \z.
    • La pattern est écrite pour décrire un format JSON de la manière la plus laxe possible, c'est à dire qu'elle autorise entre chaque token un nombre quelconque de caractères blancs. C'est pourquoi les sous-patterns sont parsemées de \s*. Mais dans ton cas, il semblerait que tous ces espaces blancs inutiles ne sont pas présents. Donc tu peux éventuellement enlever tous ces \s*. À toi de voir.
    • Pour écrire cette pattern en php, j'ai utilisé la syntaxe NOWDOC. Bien entendu, si je devais l'écrire entre quotes simples ou doubles, je devrais remplacer tous les \\ par \\\\. Maintenant je n'ai aucune idée du format attendu pour ton exercice, mais d'instinct j'utiliserai celui de la syntaxe NOWDOC.



    Pour obtenir toutes les clefs, il suffit de remplacer la pattern principale:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    $pattern = <<<'EOD'
    ~
    (?(DEFINE)
        (?<number> -?(?:[1-9][0-9]*(?:\.[0-9]+)?|0(?:\.[0-9]+)?)(?:[eE][-+]?[0-9]+)? )
        (?<null> null )
        (?<boolean> true | false )
        (?<string> " [^"\\]*+ (?:\\.[^"\\]*)*+ " )
     
        (?<list> \[ \s* (?: \g<value> (?: \s* , \s* \g<value> )*+ )?+ \s* ] )
        (?<object> { \s* (?: \g<keyval> (?: \s* , \s* \g<keyval> )*+ )?+ \s* } )
     
        (?<value> \g<number> | \g<null> | \g<boolean> | \g<string> | \g<list> | \g<object> )
        (?<keyval> \g<string> \s* : \s* \g<value> )
    )
    (?:
        \G(?!\A) : \g<object> ,
      |
        \A
        { "objekte" :
            {
                (?: \g<keyval> (?: \s* , \s* \g<keyval>)*? , "objekte" : | "objekte" : ) {
    )
    " (?<ID> [^"\\]*+ (?:\\.["\\]*)*+ ) "
    ~x
    EOD;
    if ( preg_match_all($pattern, $json, $matches) ) {
        print_r($matches['ID']);
    }


    Il s'agit là d'une structure classique utilisant l'ancre \G qui matche la position qui suit une correspondance réussie: (?: \G(?!\A) (?# préfixe des correspondances suivantes )| (?# prefixe de la première correspondance ) ) (?# ce que l'on veut ). Cette structure permet de rechercher des correspondances contiguës.

    Ensuite reste à filtrer, toutes les annonces qui ne sont pas localisées en Espagne. Pour réussir cela, il suffit de consommer toutes les annonces des autres pays avant en prenant le soin de placer l'ensemble des annonces non-voulues dans un groupe auquel on applique un quantificateur possessif pour ne pas subir le backtracking.

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    $pattern = <<<'EOD'
    ~
    (?(DEFINE)
        (?<number> -?(?:[1-9][0-9]*(?:\.[0-9]+)?|0(?:\.[0-9]+)?)(?:[eE][-+]?[0-9]+)? )
        (?<null> null )
        (?<boolean> true | false )
        (?<string> " [^"\\]*+ (?:\\.[^"\\]*)*+ " )
     
        (?<list> \[ \s* (?: \g<value> (?: \s* , \s* \g<value> )*+ )?+ \s* ] )
        (?<object> { \s* (?: \g<keyval> (?: \s* , \s* \g<keyval> )*+ )?+ \s* } )
     
        (?<value> \g<number> | \g<null> | \g<boolean> | \g<string> | \g<list> | \g<object> )
        (?<keyval> \g<string> \s* : \s* \g<value> )
    )
    (?:
        \G(?!\A) : \g<object> ,
      |
        \A
        { "objekte" :
            {
                (?: \g<keyval> (?: \s* , \s* \g<keyval>)*? , "objekte" : | "objekte" : ) {
    )
    (?:
        \g<string> : {
        (?: \g<keyval> (?: \s*, \g<keyval> )*? , "land" : | "land" : ) (?!"ESP") \g<value>
        (?: , \g<keyval> )* } ,
     
    )*+
    " (?<ID> [^"\\]*+ (?:\\.["\\]*)*+ ) "
    ~x
    EOD;
    if ( preg_match_all($pattern, $json, $matches) ) {
        print_r($matches['ID']);
    }


    ps: inutile de t'acharner à tester ces codes sur des testeurs en ligne car la chaîne est trop longue et les patterns trop complexes. Ceci dit, je rejoins Grunk en ce qui concerne l'intérêt pédagogique de s'entraîner aux regex sur des données structurées comme le JSON ou le html, alors que dans la vrai vie, il y a des parsers prévus à cet effet. (Je mettrai d'ailleurs au passage, dans le même sac les exemples de poo illustrés par des bagnoles avec options).
      2  0

  10. ###raw>post.musername###
    Expert confirmé
    La dernière pattern avec quelques améliorations (sans les espaces optionnelles et sans le groupe de capture ID):
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    ~
    (?:
        " : \g<object> ,
      |
        \A
        { "objekte" :
            {
                (?> \g<keyval> (?: , \g<keyval> )*? , "objekte" : | "objekte" : ) {
     
    )
    (?:
        \g<string> : {
        (?> \g<keyval> (?: , \g<keyval> )*? , "land" : | "land" : ) (?!"ESP") \g<value>
        (?: , \g<keyval> )* } ,
     
    )*+
     
    " \K [^"\\]*+ (?:\\.["\\]*)*+ 
     
    (?(DEFINE)
        (?<number> -?(?:[1-9][0-9]*(?:\.[0-9]+)?|0(?:\.[0-9]+)?)(?:[eE][-+]?[0-9]+)? )
        (?<string> " [^"\\]*+ (?:\\.[^"\\]*)*+ " )
     
        (?<list> \[ (?: \g<value> (?: , \g<value> )*+ )?+ ] )
        (?<object> { (?: \g<keyval> (?: , \g<keyval> )*+ )?+ } )
     
        (?<value> \g<string> | \g<object> | \g<list> | \g<number> | null | true | false )
        (?<keyval> \g<string> : \g<value> )
    )
    ~xA


    et un test.
      2  0

  11. ###raw>post.musername###
    Expert confirmé
    Citation Envoyé par CosmoKnacki Voir le message
    ...
    Merci énormément pour ton post.

    Non seulement il m'a été très utile pour ma recherche actuelle (me permettre de générer un JSON parsable par json_decode à partir d'un objet Javascript), mais en plus il m'a appris énormément de choses que j'ignorais sur les regex.

    Pour Info, bien que ce soit totalement hors sujet, je mets ici ma fonction pour transformer un objet JS en JSON :
    Code PHP :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function jsDecode($js) {
    	$pattern = <<<'EOD'
    ~
    (?(DEFINE)
    	(?<keystr> [a-zA-Z$_][a-zA-Z0-9$_]* )
    )
     
    [{[:,]\s* \K (?<quote>["']) (?<string>(?(?!\k<quote>).|(?<=\\).)*+) \k<quote>
    |
    [{,]\s* \K (?<string>\g<keystr>) (?=\s*<img src="images/smilies/icon_smile.gif" border="0" alt="" title=":)" class="inlineimg" />
     
    ~xJ
    EOD;
     
    	return json_decode( preg_replace_callback($pattern, function($match) { return '"' . $match['string'] . '"'; }, $js ), true );
    }


    Je n'ai malheureusement pas réussi à utiliser le parseur JSON en l'état, étant donné qu'il n'est pas possible de capturer des éléments dans la "Pre-Defined Subroutine", ni dans aucune backreference d'ailleurs. Cependant ce que j'ai fait semble fonctionner plutôt bien.
      0  0

  12. #12
    Modérateur

    quoteMerci énormément pour ton post.

    Non seulement il m'a été très utile pour ma recherche actuelle (me permettre de générer un JSON parsable par json_decode à partir d'un objet Javascript), mais en plus il m'a appris énormément de choses que j'ignorais sur les regex.
    [[/QUOTE]

    Euhh soit j'ai pas compris le besoin soit tu t'es drôlement compliqué la vie :

    Transformer un objet js en json
    Code js :Sélectionner tout -Visualiser dans une fenêtre à part
    const jsonstr = JSON.stringify(monObjJavascript);


    Coté PHP tu peux décoder cette chaine (sous réserve de l'avoir passé à PHP avant évidemment)
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    json_decode($jsonstr)


    Et inversement , si j'ai un JSON envoyé par mon PHP et que je veux un objet javascript :
    Code js :Sélectionner tout -Visualiser dans une fenêtre à part
    const monObjJavascript = JSON.parse(jsonstr);


    Dans tous les cas si vous utilisez des regex pour traiter du JSON y'a quelque chose de pas normal
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  13. #13
    Expert confirmé
    Citation Envoyé par grunk Voir le message
    Euhh soit j'ai pas compris le besoin soit tu t'es drôlement compliqué la vie
    Le besoin c'est que je dois prendre en entrée du PHP un fichier .js et en extraire un objet Javascript (donc pas JSON) afin d'avoir accès aux informations qu'il contient.
    Du coup le plus "simple" selon moi était de le rendre parsable en JSON en y ajoutant les guillemets aux endroits adhoc.

    Le plus simple aurait été d'avoir un parseur JSON laxiste, malheureusement très peu le sont (franchement, qu'est-ce qu'on s'en fiche que les clefs soient encadrées par des guillemets...)

  14. #14
    Modérateur

    Ok , en fait tu parses un fichier javascript , c'est pas une simple communication entre php et js. C'est en effet plus compliqué que ce que j'avais compris.

    Je sais que Service_Json de pear permet de parser des clé sans quote. Par contre c'est un vieux code , donc c'est peut être pas le top en terme de perf et de sécurité
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  15. ###raw>post.musername###
    Expert confirmé
    Citation Envoyé par Loceka
    Merci énormément pour ton post.
    De rien.

    Citation Envoyé par Loceka
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    (?<quote>["']) (?<string>(?(?!\k<quote>).|(?<=\\).)*+) \k<quote> ... ~Jx
    C'est vrai que c'est tentant de faire ça parce que c'est concis, mais c'est moins efficace que de traiter les deux cas séparément (car pas besoin de groupe quote, pas besoin de test conditionnel ni de tests avant/arrière). En plus si tu déroules la pattern, tu limiteras des coûteuses répétitions de groupes car tes quantificateurs couvriront de plus longues plages de caractères. Exemple:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    (?: " (?<string> [^"\\]*+ (?: \\. [^"\\]*)*+ ) " | ' (?<string> [^'\\]*+ (?: \\. [^'\\]*)*+ ) ' ) ... ~Jx





    Citation Envoyé par CosmoKnacki
    Ceci dit, l'impact de cette mesure reste à nuancer puisque une des particularités de pcre (contrairement à Perl) est de rendre atomique tout appel à une sous-pattern.
    Ce n'est plus vrai depuis PCRE2 10.30, la première version de PHP à en profiter est donc la version 7.3 qui installe PCRE2 10.33.

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $subject = 'AbbbAcccAdddAeeeAfffAgggAhhh';
     
    $pattern = '~^A ( [bc]+ A (?(?=c) (?1) ) .* ) A ggg~x';
     
    preg_match($pattern, $subject, $match);
     
    print_r($match);
     
    // avant PHP 7.3
    // Array
    // (
    // )
     
    // à partir de PHP 7.3
    // Array
    // (
    //     [0] => AbbbAcccAdddAeeeAfffAggg
    //     [1] => bbbAcccAdddAeeeAfff
    // )


    Avant PHP 7.3, lors de la récursion, .* consomme les caractères dddAeeeAfffAgggAhhh mais est incapable de les rendre pour que la pattern réussisse une fois sortie de la récursion.
      1  0

  16. #16
    Expert confirmé
    En effet, merci du conseil.

    C'est vrai que pour les regex je suis plus regardant sur la concision que sur les performances. Les 2 ont leurs avantages et leurs inconvénients

  17. #17
    Expert confirmé
    Attention aussi aux chaînes se terminant par un nombre pair d'antislashes.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

###raw>template_hook.ano_emploi###