Wyodrębnianie JSON z żądania klienta HTTP Raku
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 $topic
powinien 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, @array
ale wiem, że to nie jest poprawne, więc zmieniłem %topic
na $topic
.
W końcu udało mi się to, dodając .list
do linii, która definiuje, @topics
ale nie rozumiem, dlaczego to rozwiązuje, ponieważ @topics
jest 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
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 Map
nie należy umieszczać elementów w pojemnikach skalarnych, ale Hash
obiekty 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 wfor
pę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 .list
naprawia 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.