如何递归搜索 JSON 文件以查找与给定模式匹配的所有节点,并 return JSON 'path' 到节点及其值?
How do I recursively search a JSON file for all nodes matching a given pattern and return the JSON 'path' to the node and it's value?
假设我在文本文件中有 this JSON:
{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}}
使用 Perl,我使用 JSON::XS.
将文件读入名为 $json_obj
的 JSON 对象中
如何在 $json_obj
中搜索名为 name
和 return/print 的所有节点,如下所示 result/output:
widget->window->name: main_window
widget->image->name: sun1
widget->text->name: text1
备注:
- 与搜索词匹配的节点名称可以出现在树的任何级别
- 搜索词可以是纯文本或正则表达式
- 我希望能够提供我自己的分支分隔符来覆盖默认值,比如
->
- 示例
/
(为简单起见,我将其放在 perl $variable
中)
- 我希望能够在我的搜索中指定多个节点级别,以便指定
path
来匹配,例如:指定 id/colour
将 return 所有路径包含一个名为 id
的节点,该节点也是一个具有名为 colour
的子节点的父节点
- 在结果值周围显示双引号是可选的
- 我希望能够搜索多种模式,例如
/(name|alignment)/
for "find all nodes called name
or alignment
上面最后一个注释中显示搜索结果的示例:
widget->window->name: main_window
widget->image->name: sun1
widget->image->alignment: center
widget->text->name: text1
widget->text->alignment: center
由于 JSON 主要是文本,我还不确定使用 JSON::XS 的好处,所以欢迎任何关于为什么更好或更差的建议。
不言而喻,它需要递归才能搜索 n
任意层级。
这是我目前所拥有的,但我只是完成了一部分:
#!/usr/bin/perl
use 5.14.0;
use warnings;
use strict;
use IO::File;
use JSON::XS;
my $jsonfile = '/home/usr/filename.json';
my $jsonpath = 'image/src'; # example search path
my $pathsep = '/'; # for displaying results
my $fh = IO::File->new("$jsonfile", "r");
my $jsontext = join('',$fh->getlines());
$fh->close();
my $jsonobj = JSON::XS->new->utf8->pretty;
if (defined $jsonpath) {
my $perltext = $jsonobj->decode($jsontext); # is this correct?
recurse_tree($perltext);
} else {
# print file to STDOUT
say $jsontext;
}
sub recurse_tree {
my $hash = shift @_;
foreach my $key (sort keys %{$hash}) {
if ($key eq $jsonpath) {
say "$key = %{$hash}{$key} \n"; # example output
}
if (ref $hash->{$key} eq 'HASH' ||
ref $hash->{$key} eq 'ARRAY') {
recurse_tree($hash->{$key});
}
}
}
exit;
上述脚本的预期结果为:
widget/image/src: Images/Sun.png
一旦 JSON 被解码,就会有一个复杂的(嵌套的)Perl 数据结构需要搜索,而您显示的代码正是针对该结构。
但是,有些图书馆可以提供帮助;要么完全完成工作,要么提供完整、有效且经过测试的代码,您可以根据具体需求对其进行微调。
模块Data::Leaf::Walker似乎合适。一个简单的例子
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
use JSON;
use List::Util qw(any);
use Data::Leaf::Walker;
my $file = shift // 'data.json'; # provided data sample
my $json_data = do { local (@ARGV, $/) = $file; <> }; # read into a string
chomp $json_data;
my $ds = decode_json $json_data;
dd $ds; say ''; # show decoded data
my $walker = Data::Leaf::Walker->new($ds);
my $sep = '->';
while ( my ($key_path, $value) = $walker->each ) {
my @keys_in_path = @$key_path;
if (any { $_ eq 'name' } @keys_in_path) { # selection criteria
say join($sep, @keys_in_path), " => $value"
}
}
这个 'walker' 遍历数据结构,保留每个叶子的键列表。这就是使该模块特别适合您的任务的原因,以及与许多其他模块相比目的简单的原因。请参阅文档。
以上打印,针对问题中提供的示例数据
widget->window->name => main_window
widget->text->name => text1
widget->image->name => sun1
在上面的代码中选择关键路径的标准的实现相当简单,因为它检查路径中任何地方的 'name'
一次,然后打印整个路径。虽然问题没有具体说明如何处理路径中较早的匹配项或多个匹配项,但可以对此进行调整,因为我们始终拥有完整路径。
您的愿望清单的其余部分也很容易实现。仔细阅读 List::Util
和 List::MoreUtils 以获得数组分析的帮助。
另一个模块 Data::Traverse. It is particularly simple, at 70-odd lines of code 非常容易定制。
是满足可能的特定需求的一个很好的起点。
根据您的任务,您可以考虑使用 jq。此输出很简单,但您可以根据需要变得更复杂:
$ jq -r '.. | .image? | .src | strings' test.json
Images/Sun.png
$ jq -r '.. | .name? | strings' test.json
main_window
sun1
text1
遍历数据结构并没有那么糟糕,尽管前几次这样做有点奇怪。 CPAN 上有各种模块可以为您服务(如 ), but this is something you should probably know how to do on your own. We have some big examples in Intermediate Perl.
一种方法是从要处理的事物队列开始。这是迭代,而不是递归,并且根据您将元素添加到队列的方式,您可以进行深度优先或广度优先搜索。
对于每个项目,我将跟踪键到达那里的路径,以及子哈希。这就是你的递归方法的问题:你不允许跟踪路径的方法。
开始时,队列中有一个项目,因为我们在顶部。我还将定义一个目标键,因为您的问题是:
my @queue = ( { key_path => [], node => $hash } );
my $target = 'name';
接下来,我处理队列中的每个项目 (while
)。我希望 node
的每个值都是一个散列,所以我将获得该散列的所有键(foreach
)。这代表哈希的下一级。
在 foreach 中,我创建了一个新的关键路径,其中包含我正在处理的路径。我也通过使用该键获得下一个值。
在那之后,我可以做特定任务的处理。如果我找到了我的目标密钥,我会做任何我需要做的事情。在这种情况下,我输出了一些东西,但我可以添加到不同的数据结构等等。我使用 next
停止处理该密钥(尽管我可以继续)。如果我没有找到目标键,如果该值是另一个哈希引用,我会在队列中创建另一个条目。
然后,我回去处理队列。
use v5.24; # use postfix dereferencing
while( my $h = shift @queue ) {
foreach my $next_key ( keys $h->{node}->%* ) {
my $key_path = [ $h->{key_path}->@*, $next_key ];
my $value = $h->{node}{$next_key};
if( $next_key eq $target ) {
say join( "->", $key_path->@* ), " = $value";
next;
}
elsif( ref $value eq ref {} ) {
push @queue, { key_path => $key_path, node => $value };
}
}
}
我最终得到如下输出:
widget->text->name = text1
widget->image->name = sun1
widget->window->name = main_window
从那里,您可以对其进行自定义以获得您需要的其他功能。如果你想找到一个复杂的密钥,你只需要多做一点工作来比较密钥路径和你想要的。
假设我在文本文件中有 this JSON:
{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}}
使用 Perl,我使用 JSON::XS.
将文件读入名为$json_obj
的 JSON 对象中
如何在 $json_obj
中搜索名为 name
和 return/print 的所有节点,如下所示 result/output:
widget->window->name: main_window
widget->image->name: sun1
widget->text->name: text1
备注:
- 与搜索词匹配的节点名称可以出现在树的任何级别
- 搜索词可以是纯文本或正则表达式
- 我希望能够提供我自己的分支分隔符来覆盖默认值,比如
->
- 示例
/
(为简单起见,我将其放在 perl$variable
中)
- 示例
- 我希望能够在我的搜索中指定多个节点级别,以便指定
path
来匹配,例如:指定id/colour
将 return 所有路径包含一个名为id
的节点,该节点也是一个具有名为colour
的子节点的父节点
- 在结果值周围显示双引号是可选的
- 我希望能够搜索多种模式,例如
/(name|alignment)/
for "find all nodes calledname
oralignment
上面最后一个注释中显示搜索结果的示例:
widget->window->name: main_window
widget->image->name: sun1
widget->image->alignment: center
widget->text->name: text1
widget->text->alignment: center
由于 JSON 主要是文本,我还不确定使用 JSON::XS 的好处,所以欢迎任何关于为什么更好或更差的建议。
不言而喻,它需要递归才能搜索 n
任意层级。
这是我目前所拥有的,但我只是完成了一部分:
#!/usr/bin/perl
use 5.14.0;
use warnings;
use strict;
use IO::File;
use JSON::XS;
my $jsonfile = '/home/usr/filename.json';
my $jsonpath = 'image/src'; # example search path
my $pathsep = '/'; # for displaying results
my $fh = IO::File->new("$jsonfile", "r");
my $jsontext = join('',$fh->getlines());
$fh->close();
my $jsonobj = JSON::XS->new->utf8->pretty;
if (defined $jsonpath) {
my $perltext = $jsonobj->decode($jsontext); # is this correct?
recurse_tree($perltext);
} else {
# print file to STDOUT
say $jsontext;
}
sub recurse_tree {
my $hash = shift @_;
foreach my $key (sort keys %{$hash}) {
if ($key eq $jsonpath) {
say "$key = %{$hash}{$key} \n"; # example output
}
if (ref $hash->{$key} eq 'HASH' ||
ref $hash->{$key} eq 'ARRAY') {
recurse_tree($hash->{$key});
}
}
}
exit;
上述脚本的预期结果为:
widget/image/src: Images/Sun.png
一旦 JSON 被解码,就会有一个复杂的(嵌套的)Perl 数据结构需要搜索,而您显示的代码正是针对该结构。
但是,有些图书馆可以提供帮助;要么完全完成工作,要么提供完整、有效且经过测试的代码,您可以根据具体需求对其进行微调。
模块Data::Leaf::Walker似乎合适。一个简单的例子
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
use JSON;
use List::Util qw(any);
use Data::Leaf::Walker;
my $file = shift // 'data.json'; # provided data sample
my $json_data = do { local (@ARGV, $/) = $file; <> }; # read into a string
chomp $json_data;
my $ds = decode_json $json_data;
dd $ds; say ''; # show decoded data
my $walker = Data::Leaf::Walker->new($ds);
my $sep = '->';
while ( my ($key_path, $value) = $walker->each ) {
my @keys_in_path = @$key_path;
if (any { $_ eq 'name' } @keys_in_path) { # selection criteria
say join($sep, @keys_in_path), " => $value"
}
}
这个 'walker' 遍历数据结构,保留每个叶子的键列表。这就是使该模块特别适合您的任务的原因,以及与许多其他模块相比目的简单的原因。请参阅文档。
以上打印,针对问题中提供的示例数据
widget->window->name => main_window widget->text->name => text1 widget->image->name => sun1
在上面的代码中选择关键路径的标准的实现相当简单,因为它检查路径中任何地方的 'name'
一次,然后打印整个路径。虽然问题没有具体说明如何处理路径中较早的匹配项或多个匹配项,但可以对此进行调整,因为我们始终拥有完整路径。
您的愿望清单的其余部分也很容易实现。仔细阅读 List::Util
和 List::MoreUtils 以获得数组分析的帮助。
另一个模块 Data::Traverse. It is particularly simple, at 70-odd lines of code 非常容易定制。
是满足可能的特定需求的一个很好的起点。根据您的任务,您可以考虑使用 jq。此输出很简单,但您可以根据需要变得更复杂:
$ jq -r '.. | .image? | .src | strings' test.json
Images/Sun.png
$ jq -r '.. | .name? | strings' test.json
main_window
sun1
text1
遍历数据结构并没有那么糟糕,尽管前几次这样做有点奇怪。 CPAN 上有各种模块可以为您服务(如
一种方法是从要处理的事物队列开始。这是迭代,而不是递归,并且根据您将元素添加到队列的方式,您可以进行深度优先或广度优先搜索。
对于每个项目,我将跟踪键到达那里的路径,以及子哈希。这就是你的递归方法的问题:你不允许跟踪路径的方法。
开始时,队列中有一个项目,因为我们在顶部。我还将定义一个目标键,因为您的问题是:
my @queue = ( { key_path => [], node => $hash } );
my $target = 'name';
接下来,我处理队列中的每个项目 (while
)。我希望 node
的每个值都是一个散列,所以我将获得该散列的所有键(foreach
)。这代表哈希的下一级。
在 foreach 中,我创建了一个新的关键路径,其中包含我正在处理的路径。我也通过使用该键获得下一个值。
在那之后,我可以做特定任务的处理。如果我找到了我的目标密钥,我会做任何我需要做的事情。在这种情况下,我输出了一些东西,但我可以添加到不同的数据结构等等。我使用 next
停止处理该密钥(尽管我可以继续)。如果我没有找到目标键,如果该值是另一个哈希引用,我会在队列中创建另一个条目。
然后,我回去处理队列。
use v5.24; # use postfix dereferencing
while( my $h = shift @queue ) {
foreach my $next_key ( keys $h->{node}->%* ) {
my $key_path = [ $h->{key_path}->@*, $next_key ];
my $value = $h->{node}{$next_key};
if( $next_key eq $target ) {
say join( "->", $key_path->@* ), " = $value";
next;
}
elsif( ref $value eq ref {} ) {
push @queue, { key_path => $key_path, node => $value };
}
}
}
我最终得到如下输出:
widget->text->name = text1
widget->image->name = sun1
widget->window->name = main_window
从那里,您可以对其进行自定义以获得您需要的其他功能。如果你想找到一个复杂的密钥,你只需要多做一点工作来比较密钥路径和你想要的。