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

Jos*_*osh 8 raku

我无法理解这个 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>;
}
Run Code Online (Sandbox Code Playgroud)

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

Type Array does not support associative indexing.
  in block <unit> at http-clients/http.raku line 18
Run Code Online (Sandbox Code Playgroud)

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

for @topics -> %topic {
    say %topic<fancy_title>;
}
Run Code Online (Sandbox Code Playgroud)

错误消息是:

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
Run Code Online (Sandbox Code Playgroud)

如果您检查数据,它应该是一个散列,而不是一个数组。我试过@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>;
}
Run Code Online (Sandbox Code Playgroud)

有谁知道它为什么失败以及执行此任务的正确方法?

use*_*601 8

发生的事情是你创建了一个数组的数组,当你说

my @topics = %data<topic_list><topics>;
Run Code Online (Sandbox Code Playgroud)

这不是这些模块独有的,而是在 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]]
Run Code Online (Sandbox Code Playgroud)

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

  • 绑定数组
    而不是使用@b = %x<y>,您使用@b := %x<y>. 绑定到@-sigiled 变量会自动解除容器化。
  • 使用 zen 运算符
    当您想避免将列表/哈希值视为一个的可能性时,您可以使用 zen 切片来删除任何容器(如果它碰巧在那里)。这是可以做到无论是在分配(@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>;
}
Run Code Online (Sandbox Code Playgroud)

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

for @topics<> -> $topic {
    say $topic<fancy_title>;
}
Run Code Online (Sandbox Code Playgroud)

.list修复问题的原因- 正如您在回答其余部分之后可能猜测的那样 - 是它返回一个不在容器中的新列表。

  • 谢谢。实际上,由于拼写错误,我曾经做过类似“%data&lt;topic_list&gt;&lt;topics&gt;[]”的操作(一定是没有按“0”足够用力)并且它有效,但我认为这是一个意外的怪癖这种语言可能不是正确的做事方式。:) 我将尝试你提到的两种方法。 (2认同)
  • 如果您在某些情况下直接执行“for”循环,则 @raiph decont 是必要的。我知道我曾经遇到过一两次“for foo.bar(…)&lt;&gt; { … }”。但老实说,使用其他一些技术可以使代码更加明确,特别是“.list” (2认同)