Wyodrębnianie JSON z żądania klienta HTTP Raku

Nov 26 2020

Mam problem ze zrozumieniem, co jest nie tak z tym kodem Raku.

Chcę pobrać JSON ze strony internetowej i wydrukować pole z każdego elementu w tablicy w formacie JSON (w tym przypadku tytuły najnowszych tematów z dowolnego forum Discourse).

To jest kod, który spodziewałem się zadziałać, ale się nie udało:

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>;
}

Komunikat o błędzie pochodzi z say $topic<fancy_title>wiersza:

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

Spodziewałbym się, że $topicpowinien być zapisany jako %topic, ponieważ jest to tablica skrótów, ale to nie działa:

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

Komunikat o błędzie to:

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

Jeśli sprawdzasz dane, powinien to być skrót, a nie tablica. Próbowałem, @arrayale wiem, że to nie jest poprawne, więc zmieniłem %topicna $topic.

W końcu udało mi się to, dodając .listdo linii, która definiuje, @topicsale nie rozumiem, dlaczego to rozwiązuje, ponieważ @topicsjest to, (Array)czy to jest dodawane, czy nie.

Oto działający kod:

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>;
}

Czy ktoś wie, dlaczego zawodzi i jak prawidłowo wykonać to zadanie?

Odpowiedzi

8 user0721090601 Nov 26 2020 at 07:06

To, co się stało, to że utworzyłeś tablicę tablicy, kiedy mówisz

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

Nie jest to unikalne dla tych modułów, ale ogólnie w Raku z przypisaniami tablic.

Weźmy prostszy hash, aby zobaczyć, co się dzieje:

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

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

Połów polega na tym, że przypisanie tablicy (które jest używane, gdy zmienna ma @sigil) interpretuje %x<y> jako pojedynczy element, ponieważ znajduje się w kontenerze skalarnym, który następnie szczęśliwie umieszcza @b[0]. Chociaż nie można kontrolować samego modułu, można zobaczyć różnicę w moim przykładzie, jeśli powiesz my %x is Map = …, jak Mapnie należy umieszczać elementów w pojemnikach skalarnych, ale Hashobiekty zrobić. Istnieją dwa sposoby, aby powiedzieć Raku, aby traktował pojedynczy przedmiot jako jego zawartość, a nie jako pojedynczy pojemnik.

  • Powiąż tablicę
    Zamiast używać @b = %x<y>, używasz @b := %x<y>. Powiązanie ze @zmiennymi -sigiled jest automatycznie dekontaineryzowane.
  • Użyj operatora zen
    Jeśli chcesz uniknąć możliwości traktowania wartości listy / skrótu jako jedności, możesz użyć wycinka zen, aby usunąć dowolny kontener, jeśli się tam znajduje. Można to wykonać albo w przypisania ( @b = %x<y>[]) lub w forpętli ( for @b[] -> $b). Należy zauważyć, że <>, []i {}są praktycznie synonimami, bez względu na rzeczywisty typ - większość ludzi po prostu użyć jednego, który pasuje do poprzedniego.

Więc w swoim kodzie możesz po prostu zrobić:

...
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>;
}

Lub w pętli, jako opcja 3:

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

Powodem, dla którego .listnaprawia rzeczy - jak można się prawdopodobnie domyślić po dalszej części odpowiedzi - jest to, że zwraca nową listę, której nie ma w kontenerze.