Extrayendo JSON de una solicitud de cliente HTTP Raku

Nov 26 2020

Tengo problemas para entender qué está mal con este código Raku.

Quiero obtener JSON de un sitio web e imprimir un campo de cada elemento en una matriz dentro de JSON (en este caso, los títulos de los temas más recientes de cualquier foro de Discourse).

Este es el código que esperaba que funcionara, pero falló:

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

El mensaje de error es de la say $topic<fancy_title>línea:

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

Hubiera esperado que $topicse escribiera como %topic, porque es una matriz de hashes, pero esto no funciona:

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

El mensaje de error para eso es:

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 inspecciona los datos, debe ser un hash, no una matriz. Lo intenté @arraypero sé que no es correcto, así que cambié %topica $topic.

Finalmente lo hice funcionar agregando .lista la línea que define, @topicspero no entiendo por qué eso lo arregla, porque @topicses un (Array)si eso se agrega o no.

Este es el código de trabajo:

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

¿Alguien sabe por qué está fallando y la forma correcta de realizar esta tarea?

Respuestas

8 user0721090601 Nov 26 2020 at 07:06

Lo que sucedió es que ha creado una matriz de una matriz cuando dice

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

Esto no es exclusivo de estos módulos, sino general en Raku con asignaciones de matrices.

Echemos un hash más simple para ver qué está pasando:

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

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

El problema es que la asignación de matriz (que se usa cuando la variable tiene el @sigilo) se interpreta %x<y> como un solo elemento ya que está en un contenedor escalar, que luego coloca felizmente @b[0]. Aunque no se puede controlar el módulo en sí, se puede ver la diferencia en mi ejemplo, si usted dice my %x is Map = …que Mapno coloque artículos en contenedores escalares, pero Hashlos objetos hacerlo. Hay dos formas de decirle a Raku que trate el elemento único como su contenido, en lugar de un contenedor único.

  • Vincular la matriz
    En lugar de usar @b = %x<y>, usa @b := %x<y>. La vinculación a @variables signadas se descontaineriza automáticamente.
  • Utilice un operador zen
    Cuando desee evitar la posibilidad de que un valor de lista / hash sea tratado como uno, puede utilizar un segmento zen para eliminar cualquier contenedor si se encuentra allí. Esto se puede hacer ya sea en asignación ( @b = %x<y>[]) o en el forbucle ( for @b[] -> $b). Tenga en cuenta que <>, []y {}son efectivamente sinónimos, independientemente del tipo real: la mayoría de las personas solo usan el que coincide con el anterior.

Entonces, en su código, podría hacer:

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

O en tu bucle, como opción 3:

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

La razón por la que .listarregla las cosas, como probablemente pueda suponer después del resto de la respuesta, es que devuelve una lista nueva que no está en un contenedor.