Extração de JSON de uma solicitação de cliente Raku HTTP
Estou tendo problemas para entender o que há de errado com este código Raku.
Desejo buscar JSON em um site da Web e imprimir um campo de cada item em uma matriz dentro do JSON (neste caso, os títulos dos tópicos mais recentes de qualquer fórum do Discourse).
Este é o código que eu esperava que funcionasse, mas falhou:
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>;
}
A mensagem de erro vem da say $topic<fancy_title>
linha:
Type Array does not support associative indexing.
in block <unit> at http-clients/http.raku line 18
Eu esperava que $topic
fosse escrito como %topic
, porque é uma matriz de hashes, mas isso não funciona:
for @topics -> %topic {
say %topic<fancy_title>;
}
A mensagem de erro para isso é:
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
Se você inspecionar os dados, deve ser um hash, não um array. Tentei, @array
mas sei que não está correto, então mudei %topic
para $topic
.
Eu finalmente consegui fazer funcionar adicionando .list
à linha que define, @topics
mas não entendo por que isso corrige, porque @topics
é um (Array)
se isso é adicionado ou não.
Este é o código de trabalho:
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>;
}
Alguém sabe por que está falhando e a maneira correta de realizar essa tarefa?
Respostas
O que aconteceu é que você criou uma matriz de uma matriz quando diz
my @topics = %data<topic_list><topics>;
Isso não é exclusivo para esses módulos, mas geral em Raku com atribuições de array.
Vamos pegar um hash mais simples para ver o que está acontecendo:
my %x = y => [1,2,3];
my $b = %x<y>; my @b = %x<y>; say $b; # [1 2 3]
say @b; # [[1 2 3]]
O problema é que a atribuição de array (que é usada quando a variável tem o @
sigilo) é interpretada %x<y>
como um único item , pois está em um contêiner escalar, que então felizmente o coloca @b[0]
. Embora você não possa controlar o próprio módulo, pode ver a diferença no meu exemplo se disser my %x is Map = …
como Map
não coloque itens em contêineres escalares, mas os Hash
objetos sim. Existem duas maneiras de dizer a Raku para tratar um único item como seu conteúdo, em vez de um único recipiente.
- Vincule a matriz
Em vez de usar@b = %x<y>
, você usa@b := %x<y>
. A vinculação a@
variáveis -sigiled é descontentada automaticamente. - Usar um operador zen
Quando quiser evitar a possibilidade de um valor de lista / hash ser tratado como um, você pode usar uma fatia zen para remover qualquer contêiner, se ele estiver lá. Isto pode ser feito quer a designação (@b = %x<y>[]
) ou nofor
circuito (for @b[] -> $b
). Observe que<>
,[]
e{}
são efetivamente sinônimos, independentemente do tipo real - a maioria das pessoas apenas usa aquele que corresponde ao anterior.
Portanto, em seu código, você pode apenas fazer:
...
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 em seu loop, como opção 3:
for @topics<> -> $topic { say $topic<fancy_title>;
}
O motivo pelo qual .list
corrige as coisas - como você provavelmente pode supor após o resto da resposta - é que ele retorna uma lista nova que não está em um contêiner.