Extraction de JSON à partir d'une requête client HTTP Raku

Nov 26 2020

J'ai du mal à comprendre ce qui ne va pas avec ce code Raku.

Je souhaite récupérer JSON à partir d'un site Web et imprimer un champ de chaque élément d'un tableau dans le JSON (dans ce cas, les titres des derniers sujets de n'importe quel forum Discourse).

C'est du code que je m'attendais à fonctionner, mais il a échoué:

use HTTP::UserAgent;
use JSON::Tiny;

my $client = HTTP::UserAgent.new; $client.timeout = 10;

my $url = 'https://meta.discourse.org/latest.json'; my $resp = $client.get($url);

my %data = from-json($resp.content); # I think the problem starts here. my @topics = %data<topic_list><topics>; say @topics.WHAT; #=> (Array) for @topics -> $topic {
    say $topic<fancy_title>;
}

Le message d'erreur provient de la say $topic<fancy_title>ligne:

Type Array does not support associative indexing.
  in block <unit> at http-clients/http.raku line 18

Je m'attendais à ce que cela $topicsoit écrit comme %topic, car c'est un tableau de hachages, mais cela ne fonctionne pas:

for @topics -> %topic {
    say %topic<fancy_title>;
}

Le message d'erreur pour cela est:

Type check failed in binding to parameter '%topic'; expected Associative but got Array ([{:archetype("regula...)
  in block <unit> at http-clients/http.raku line 17

Si vous inspectez les données, il doit s'agir d'un hachage et non d'un tableau. J'ai essayé @arraymais je sais que ce n'est pas correct, alors j'ai changé %topicpour $topic.

Je l'ai finalement fait fonctionner en ajoutant .listà la ligne qui définit @topicsmais je ne comprends pas pourquoi cela le corrige, car @topicsc'est un (Array)ajout ou non.

Voici le code de travail:

use HTTP::UserAgent;
use JSON::Tiny;

my $client = HTTP::UserAgent.new; $client.timeout = 10;

my $url = 'https://meta.discourse.org/latest.json'; my $resp = $client.get($url);

my %data = from-json($resp.content); # Adding `.list` here makes it work, but the type doesn't change. # Why is `.list` needed? my @topics = %data<topic_list><topics>.list; say @topics.WHAT; #=> (Array) # Why is it `$topic` instead of `%topic`?
for @topics -> $topic { say $topic<fancy_title>;
}

Quelqu'un sait-il pourquoi il échoue et la bonne façon d'effectuer cette tâche?

Réponses

8 user0721090601 Nov 26 2020 at 07:06

Ce qui s'est passé, c'est que vous avez créé un tableau d'un tableau lorsque vous dites

my @topics = %data<topic_list><topics>;

Ce n'est pas unique à ces modules, mais général à travers Raku avec les attributions de baies.

Prenons un hachage plus simple pour voir ce qui se passe:

my %x = y => [1,2,3];

my $b = %x<y>; my @b = %x<y>; say $b;  # [1 2 3]
say @b;  # [[1 2 3]]

Le hic, c'est que l'affectation de tableau (qui est utilisée lorsque la variable a le @sigil) interprète %x<y> comme un élément unique car il est dans un conteneur scalaire, qu'il place ensuite avec plaisir @b[0]. Bien que vous ne puissiez pas contrôler le module lui-même, vous pouvez voir la différence dans mon exemple si vous dites my %x is Map = …que Mapne placez pas les éléments dans des conteneurs scalaires, mais que les Hashobjets le font. Il y a deux façons de dire à Raku de traiter l'élément unique comme son contenu, plutôt que comme un seul conteneur.

  • Liez le tableau
    Au lieu d'utiliser @b = %x<y>, vous utilisez @b := %x<y>. La liaison aux @variables -sigiled se décontainérise automatiquement.
  • Utiliser un opérateur zen
    Lorsque vous voulez éviter la possibilité qu'une liste / valeur de hachage soit traitée comme une seule, vous pouvez utiliser une tranche zen pour supprimer tout conteneur s'il y en a. Cela peut être fait soit à affectation ( @b = %x<y>[]), soit à la forboucle ( for @b[] -> $b). Notez que <>, []et {}sont en fait synonymes, quel que soit le type réel - la plupart des gens utilisent simplement celui qui correspond au précédent.

Donc, dans votre code, vous pouvez simplement faire:

...
my %data = from-json($resp.content);

my @topics := %data<topic_list><topics>;   # (option 1) binding
my @topics  = %data<topic_list><topics><>; # (option 2) zen slice

for @topics -> $topic { say $topic<fancy_title>;
}

Ou dans votre boucle, comme option 3:

for @topics<> -> $topic { say $topic<fancy_title>;
}

La raison pour laquelle les .listchoses corrigent - comme vous pouvez probablement le supposer après le reste de la réponse - est qu'elle renvoie une nouvelle liste qui n'est pas dans un conteneur.