Say I have this JSON in a text file:
{"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;"
}
}}
Using Perl I have read the file into a JSON object called $json_obj
using JSON::XS.
How do I search $json_obj
for all nodes called name
and return/print the following as the result/output:
widget->window->name: main_window
widget->image->name: sun1
widget->text->name: text1
Notes:
->
/
(for simplicity, I'll just put this in a perl $variable
)path
to match, for example: specifying id/colour
would return all paths that contain a node called id
that is also a parent with a child node called colour
/(name|alignment)/
for "find all nodes called name
or alignment
Example showing results of search in last note above:
widget->window->name: main_window
widget->image->name: sun1
widget->image->alignment: center
widget->text->name: text1
widget->text->alignment: center
Since JSON is mostly just text, I'm not yet sure of the benefit of even using JSON::XS so any advice on why this is better or worse is most welcome.
It goes without saying that it needs to be recursive so it can search n
arbitrary levels deep.
This is what I have so far, but I'm only part way there:
#!/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;
The expected result from the above script would be:
widget/image/src: Images/Sun.png
Once that JSON is decoded, there is a complex (nested) Perl data structure that you want to search through, and the code you show is honestly aiming for that.
However, there are libraries out there which can help; either to do the job fully or to provide complete, working, and tested code that you can fine tune to the exact needs.
The module Data::Leaf::Walker seems suitable. A simple example
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"
}
}
This 'walker' goes through the data structure, keeping the list of keys to each leaf. This is what makes this module particularly suitable for your quest, along with its simplicity of purpose in comparison to many others. See documentation.
The above prints, for the sample data provided in the question
widget->window->name => main_window widget->text->name => text1 widget->image->name => sun1
The implementation of the criterion for which key-paths get selected in the code above is rather simple-minded, since it checks for 'name'
anywhere in the path, once, and then prints the whole path. While the question doesn't specify what to do about matches earlier in the path, or with multiple ones, this can be adjusted since we always have the full path.
The rest of your wish list is fairly straightforward to implement as well. Peruse List::Util
and List::MoreUtils for help with array analysis.
Another module, that is a great starting point for possible specific needs, is Data::Traverse. It is particularly simple, at 70-odd lines of code, so very easy to customize.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With