Spiros Papageorgiou
Spiros Papageorgiou

Reputation: 33

In Perl, how can query a JSON::Path object and return the paths of the matched elements?

I have a json structure loaded on to a hash, like this:

my $data = {
 "store1" => {
   "book" => [ 
     { "category" =>  "reference",
       "author"   =>  "Nigel Rees",
       "title"    =>  "Sayings of the Century",
       "price"    =>  8.95,
     },
     { "category" =>  "fiction",
       "author"   =>  "Herman Melville",
       "title"    =>  "Moby Dick",
       "isbn"     =>  "0-553-21311-3",
       "price"    =>  8.99,
     },
   ],
   "bicycle" => [
     { "color" => "red",
       "price" => 19.95,
     },
   ],
 },
 "store2" => {
   "book" => [ 
     { "category" =>  "reference",
       "author"   =>  "Nigel Rees",
       "title"    =>  "Sayings of the Century",
       "price"    =>  8.95,
     },
     { "category" =>  "fiction",
       "author"   =>  "Herman Melville",
       "title"    =>  "Moby Dick",
       "isbn"     =>  "0-553-21311-3",
       "price"    =>  8.99,
     },
   ],
   "bicycle" => [
     { "color" => "red",
       "price" => 19.95,
     },
   ],
 },

};

Tools like JSON::Path, return the values of the elements matching the path.

my $jpath   = JSON::Path->new('$.*.book[*]');
my @books   = $jpath->values($data);

I would like to get the Paths, not the values. Something like:

[
'$->{store}{book}[0]',
'$->{store}{book}[1]',
'$->{store}{book}[2]'
]

I want to find information in the Path, in order to know the location of the results and not the results themselves. For example, given the query:

my $jpath   = JSON::Path->new('$.*.book[*]');

I would like to know if the Path contains "store1" or "store2", so I need the query to return the Paths.

Is there a way to do this?

Thanx, Spiros

Upvotes: 3

Views: 298

Answers (1)

Sinan Ünür
Sinan Ünür

Reputation: 118158

You should be able to use paths.

#!/usr/bin/env perl

use strict;
use warnings;
use feature 'say';

my $data = {
 "store" => {
   "book" => [ 
     { "category" =>  "reference",
       "author"   =>  "Nigel Rees",
       "title"    =>  "Sayings of the Century",
       "price"    =>  8.95,
     },
     { "category" =>  "fiction",
       "author"   =>  "Evelyn Waugh",
       "title"    =>  "Sword of Honour",
       "price"    =>  12.99,
     },
     { "category" =>  "fiction",
       "author"   =>  "Herman Melville",
       "title"    =>  "Moby Dick",
       "isbn"     =>  "0-553-21311-3",
       "price"    =>  8.99,
     },
     { "category" =>  "fiction",
       "author"   =>  "J. R. R. Tolkien",
       "title"    =>  "The Lord of the Rings",
       "isbn"     =>  "0-395-19395-8",
       "price"    =>  22.99,
     },
   ],
   "bicycle" => [
     { "color" => "red",
       "price" => 19.95,
     },
   ],
 },
};
 
# All books in the store
my $jpath   = JSON::Path->new('$.store.book[*]');
my @books   = $jpath->paths($data);
say for @books;

Output:

$['store']['book']['0']
$['store']['book']['1']
$['store']['book']['2']
$['store']['book']['3']

Now, in this case, you seem to want to have a list of stores with out using a data structure appropriate for storing lists. That is, it would be more appropriate to have:

my $data = {
  stores => [
  # each store is product type to list of
  # products of that type in that store
  ]
};

Each store should probably also have a name property.

I am assuming in real life you are not emulating arrays with integer suffixes in the name. In that case, the top level keys can be arbitrary strings. So, using the $data structure you provided,

# All books in all stores
my $jpath   = JSON::Path->new('$.*.book[*]');
my @books   = $jpath->paths($data);
say for @books;

Output:

$['store1']['book']['0']
$['store1']['book']['1']
$['store2']['book']['0']
$['store2']['book']['1']

Upvotes: 3

Related Questions