从 Raku HTTP 客户端请求中提取 JSON

Extracting JSON from a Raku HTTP client request

我无法理解这个 Raku 代码有什么问题。

我想从网站上获取 JSON,并打印 JSON 中数组中每个项目的字段(在本例中是来自任何 Discourse 论坛的最新主题的标题) .

这是我希望工作的代码,但它失败了:

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

错误消息来自 say $topic<fancy_title> 行:

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

我本来希望 $topic 应该写成 %topic,因为它是一个哈希数组,但这不起作用:

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

错误信息是:

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

如果您检查数据,它应该是散列,而不是数组。我试过 @array 但我知道那是不正确的,所以我将 %topic 更改为 $topic

我终于通过将 .list 添加到定义 @topics 的行来使其工作,但我不明白为什么会修复它,因为 @topics 是一个 (Array) 是否添加。

这是工作代码:

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

有谁知道失败的原因以及执行此任务的正确方法?

发生的事情是当你说

时你已经创建了一个数组的数组
my @topics = %data<topic_list><topics>;

这不是这些模块独有的,而是整个 Raku 数组分配的普遍情况。

让我们用一个更简单的哈希看看是怎么回事:

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

my $b = %x<y>;
my @b = %x<y>;

say $b;  # [1 2 3]
say @b;  # [[1 2 3]]

问题是数组赋值(当变量具有 @ 印记时使用)将 %x<y> 解释为单个项目 在一个标量容器中,然后它愉快地放入 @b[0]。虽然您无法控制模块本身,但如果您说 my %x is Map = … 因为 Map 不将项目放置在标量容器中,但 Hash 对象可以,您可以看到我的示例中的差异。有两种方法可以告诉 Raku 将单个项目视为其内容,而不是单个容器。

  • 绑定数组
    您不使用 @b = %x<y>,而是使用 @b := %x<y>。绑定到 @-sigiled 变量会自动去容器化。
  • 使用禅运算符
    当您想避免 list/hash 值被视为一个值的可能性时,您可以使用 zen slice 删除恰好存在的任何容器。这可以在 赋值 (@b = %x<y>[]) for 循环 (for @b[] -> $b) 中完成.请注意,<>[]{} 实际上是同义词,无论实际类型如何 - 大多数人只使用与前一个匹配的那个。

所以在你的代码中,你可以这样做:

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

或者在你的循环中,作为选项 3:

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

.list 解决问题的原因 — 正如您在回答其余部分后可能推测的那样 — 是 returns 一个不在容器中的新列表。