package JSON::Path;
$JSON::Path::VERSION = '1.0.0';
use strict;
use warnings;
# VERSION
use Exporter::Shiny qw/ jpath jpath1 jpath_map /;
our $AUTHORITY = 'cpan:POPEFELIX';
our $Safe = 1;
use Carp;
use JSON::MaybeXS qw/decode_json/;
use JSON::Path::Evaluator;
use Scalar::Util qw[blessed];
use LV ();
use overload '""' => \&to_string;
sub jpath {
my ( $object, $expression ) = @_;
my @return = __PACKAGE__->new($expression)->values($object);
}
sub jpath1 : lvalue {
my ( $object, $expression ) = @_;
__PACKAGE__->new($expression)->value($object);
}
sub jpath_map (&$$) {
my ( $coderef, $object, $expression ) = @_;
return __PACKAGE__->new($expression)->map( $object, $coderef );
}
sub new {
my ( $class, $expression ) = @_;
return $expression
if blessed($expression) && $expression->isa(__PACKAGE__);
return bless \$expression, $class;
}
sub to_string {
my ($self) = @_;
return $$self;
}
sub paths {
my ( $self, $object ) = @_;
my @paths = JSON::Path::Evaluator::evaluate_jsonpath( $object, "$self", want_path => 1);
return @paths;
}
sub get {
my ( $self, $object ) = @_;
my @values = $self->values($object);
return wantarray ? @values : $values[0];
}
sub set {
my ( $self, $object, $value, $limit ) = @_;
if ( !ref $object ) {
# warn if not called internally. If called internally (i.e. from value()) we will already have warned.
my @c = caller(0);
if ( $c[1] !~ /JSON\/Path\.pm$/ ) {
carp qq{Useless attempt to set a value on a non-reference};
}
}
my $count = 0;
my @refs = JSON::Path::Evaluator::evaluate_jsonpath( $object, "$self", want_ref => 1 );
for my $ref (@refs) {
${$ref} = $value;
++$count;
last if $limit && ( $count >= $limit );
}
return $count;
}
sub value : lvalue {
my ( $self, $object ) = @_;
LV::lvalue(
get => sub {
my ($value) = $self->get($object);
return $value;
},
set => sub {
my $value = shift;
# do some caller() magic to warn at the right place
if ( !ref $object ) {
my @c = caller(2);
my ( $filename, $line ) = @c[ 1, 2 ];
warn qq{Useless attempt to set a value on a non-reference at $filename line $line\n};
}
$self->set( $object, $value, 1 );
},
);
}
sub values {
my ( $self, $object ) = @_;
croak q{non-safe evaluation, died} if "$self" =~ /\?\(/ && $JSON::Path::Safe;
return JSON::Path::Evaluator::evaluate_jsonpath( $object, "$self", script_engine => 'perl' );
}
sub map {
my ( $self, $object, $coderef ) = @_;
my $count;
foreach my $path ( $self->paths( $object ) ) {
my ($ref) = JSON::Path::Evaluator::evaluate_jsonpath( $object, $path, want_ref => 1 );
++$count;
my $value = do {
no warnings 'numeric';
local $_ = ${$ref};
local $. = $path;
scalar $coderef->();
};
${$ref} = $value;
}
return $count;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSON::Path
=head1 VERSION
version 1.0.0
=head1 SYNOPSIS
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,
},
],
},
};
use JSON::Path 'jpath_map';
# All books in the store
my $jpath = JSON::Path->new('$.store.book[*]');
my @books = $jpath->values($data);
# The author of the last (by order) book
my $jpath = JSON::Path->new('$..book[-1:].author');
my $tolkien = $jpath->value($data);
# Convert all authors to uppercase
jpath_map { uc $_ } $data, '$.store.book[*].author';
=head1 DESCRIPTION
This module implements JSONPath, an XPath-like language for searching
JSON-like structures.
JSONPath is described at L.
=head2 Constructor
=over 4
=item C<< JSON::Path->new($string) >>
Given a JSONPath expression $string, returns a JSON::Path object.
=back
=head2 Methods
=over 4
=item C<< values($object) >>
Evaluates the JSONPath expression against an object. The object $object
can be either a nested Perl hashref/arrayref structure, or a JSON string
capable of being decoded by JSON::MaybeXS::decode_json.
Returns a list of structures from within $object which match against the
JSONPath expression. In scalar context, returns the number of matches.
=item C<< value($object) >>
Like C, but returns just the first value. This method is an lvalue
sub, which means you can assign to it:
my $person = { name => "Robert" };
my $path = JSON::Path->new('$.name');
$path->value($person) = "Bob";
TAKE NOTE! This will create keys in $object. E.G.:
my $obj = { foo => 'bar' };
my $path = JSON::Path->new('$.baz');
$path->value($obj) = 'bak'; # $obj->{baz} is created and set to 'bak';
=item C<< paths($object) >>
As per C but instead of returning structures which match the
expression, returns canonical JSONPaths that point towards those structures.
=item C<< get($object) >>
In list context, identical to C<< values >>, but in scalar context returns
the first result.
=item C<< set($object, $value, $limit) >>
Alters C<< $object >>, setting the paths to C<< $value >>. If set, then
C<< $limit >> limits the number of changes made.
TAKE NOTE! This will create keys in $object. E.G.:
my $obj = { foo => 'bar' };
my $path = JSON::Path->new('$.baz');
$path->set($obj, 'bak'); # $obj->{baz} is created and set to 'bak'
Returns the number of changes made.
=item C<< map($object, $coderef) >>
Conceptually similar to Perl's C