Extração de JSON de uma solicitação de cliente Raku HTTP

Nov 26 2020

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 $topicfosse 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, @arraymas sei que não está correto, então mudei %topicpara $topic.

Eu finalmente consegui fazer funcionar adicionando .listà linha que define, @topicsmas 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

8 user0721090601 Nov 26 2020 at 07:06

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 Mapnão coloque itens em contêineres escalares, mas os Hashobjetos 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 no forcircuito ( 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 .listcorrige 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.