Group
Extension

JQ-Lite/lib/JQ/Lite.pm

package JQ::Lite;

use strict;
use warnings;

use JSON::PP ();

use JQ::Lite::Filters;
use JQ::Lite::Parser;
use JQ::Lite::Util ();

our $VERSION = '1.32';

sub new {
    my ($class, %opts) = @_;
    my $self = {
        raw => $opts{raw} || 0,
        _vars => {},
    };
    return bless $self, $class;
}

sub run_query {
    my ($self, $json_text, $query) = @_;
    my $data = JQ::Lite::Util::_decode_json($json_text);

    return ($data) if !defined $query || $query =~ /^\s*\.\s*$/;

    my @parts = JQ::Lite::Parser::parse_query($query);

    my @results = ($data);
    for my $part (@parts) {
        my @next_results;

        if (JQ::Lite::Filters::apply($self, $part, \@results, \@next_results)) {
            @results = @next_results;
            next;
        }

        for my $item (@results) {
            push @next_results, JQ::Lite::Util::_traverse($item, $part);
        }
        @results = @next_results;
    }

    return @results;
}

1;
__END__

=encoding utf-8

=head1 NAME

JQ::Lite - A lightweight jq-like JSON query engine in Perl

=head1 VERSION

Version 1.32

=head1 SYNOPSIS

  use JQ::Lite;

  my $jq = JQ::Lite->new;
  my @names = $jq->run_query($json_text, '.users[].name');

  say encode_json($_) for @names;

Command-line usage mirrors the library API:

  cat users.json | jq-lite '.users[].name'
  jq-lite -r '.users[] | .name' users.json

=head1 OVERVIEW

JQ::Lite is a lightweight, pure-Perl JSON query engine inspired by the
L<jq|https://stedolan.github.io/jq/> command-line tool. It gives you jq-style
queries without requiring any XS components or external binaries, so you can
embed powerful JSON exploration directly inside Perl scripts or run it from the
included command-line client.

=head1 GETTING STARTED

=over 4

=item 1. Decode or obtain a JSON string.

  my $json_text = do { local $/; <DATA> };

=item 2. Build a JQ::Lite instance (enable C<raw => 1> to receive raw scalars
instead of JSON-encoded structures).

  my $jq = JQ::Lite->new(raw => 1);

=item 3. Run a jq-style query and iterate over the returned Perl values.

  for my $value ($jq->run_query($json_text, '.users[] | select(.active)')) {
      say $value->{name};
  }

=back

Need a REPL-style experience? Run C<jq-lite> with no query to enter interactive
mode, then experiment with filters until you find the data you need.

=head1 FEATURES AT A GLANCE

=over 4

=item * Pure Perl implementation – no non-core dependencies or XS

=item * Familiar jq-inspired traversal syntax (C<.foo>, C<.foo[]>, C<.foo?>,
array slicing)

=item * Extensive library of filters for aggregations, reshaping, and string
manipulation

=item * Pipeline-friendly design that mirrors jq's mental model

=item * Command-line wrapper with colour output, YAML support, and decoder
selection

=item * Interactive shell for exploring queries line-by-line

=back

=head1 LIBRARY INTERFACE

=head2 new

  my $jq = JQ::Lite->new;

Creates a new instance. Pass C<raw => 1> to receive plain scalars (like jq's
C<-r>) instead of JSON-encoded structures. Additional options may be added in
future versions.

=head1 METHODS

=head2 run_query

  my @results = $jq->run_query($json_text, $query);

Runs a jq-like query against the given JSON string.
Returns a list of matched results. Each result is a Perl scalar
(string, number, arrayref, hashref, etc.) depending on the query.

In scalar context the first result is returned, mirroring jq's behaviour.

=head1 COMMAND-LINE CLIENT

The distribution ships with C<jq-lite>, a drop-in jq-style executable that reads
JSON from STDIN or files, honours common jq flags (C<-r>, C<-c>, C<-n>, C<-s>),
and supports coloured output. Run C<jq-lite --help> for the full option list, or
C<jq-lite --help-functions> to display every built-in helper.

=head1 SUPPORTED SYNTAX

=head2 Traversal and extraction

=over 4

=item * C<.key.subkey>

=item * C<.array[0]> (index access)

=item * C<.array[]> (flatten arrays)

=item * C<.key?> (optional key access without throwing errors)

=back

=head2 Filtering and control flow

=over 4

=item * C<select(.key > 1 and .key2 == "foo")>

Evaluates jq-style conditional expressions. Conditions are treated as filters
executed against the current input; the first branch whose condition produces a
truthy result has its filter evaluated and emitted. Optional C<elif> clauses
cascade additional tests, and the optional C<else> filter runs only when no
prior branch matched. When no C<else> clause is supplied and every condition is
falsey the expression yields no output.

Example:

  if .score >= 90 then "A"
  elif .score >= 80 then "B"
  else "C"
  end

=item * C<reduce expr as $var (init; update)> (accumulate values with lexical
bindings)

=item * C<foreach expr as $var (init; update [; extract])> (stream results while
folding values)

=back

=head2 Aggregation and grouping

=over 4

=item * C<group_by(.field)>

=item * C<group_count(.field)>

=item * C<sort_by(.key)>, C<sort_desc()>

Sort array elements in descending order using smart numeric/string comparison.

Example:

  .scores | sort_desc

Returns:

  [100, 75, 42, 12]

=item * C<unique_by(.key)>

=item * C<.key | count> (count items or fields)

=item * C<.[] | select(...) | count> (combine flattening + filter + count)

=item * C<sum_by(.field)>, C<avg_by(.field)>, C<median_by(.field)>,
C<percentile(p)>, C<min_by(.field)>, C<max_by(.field)>

Project numeric values from each element and compute aggregated statistics
(sum, average, median, percentiles, min/max, etc.).

=back

=head2 String and array helpers

=over 4

=item * C<.array | map(.field) | join(", ")>

Concatenate array elements with a custom separator string.

Example:

  .users | map(.name) | join(", ")

Results in:

  "Alice, Bob, Carol"

=item * C<split(separator)>

Split string values (and arrays of strings) using a literal separator.

Example:

  .users[0].name | split("")

Results in:

  ["A", "l", "i", "c", "e"]

=item * C<explode()>

Convert strings into arrays of Unicode code points. When applied to arrays the
conversion happens element-wise, while non-string values (including hashes) are
passed through untouched. This mirrors jq's C<explode> helper and pairs with
C<implode> for round-trip transformations.

Example:

  .title | explode

Returns:

  [67, 79, 68, 69]

=item * C<implode()>

Perform the inverse of C<explode> by turning arrays of Unicode code points back
into strings. Nested arrays are processed recursively so pipelines like
C<explode | implode> work over heterogeneous structures. Non-array inputs pass
through unchanged.

Example:

  .codes | implode

Returns:

  "CODE"

=item * C<keys_unsorted()>

Returns the keys of an object without sorting them, mirroring jq's
C<keys_unsorted> helper. Arrays yield their zero-based indices, while
non-object/array inputs return C<undef> to match the behaviour of C<keys>.

Example:

  .profile | keys_unsorted

=item * C<values()>

Returns all values of a hash as an array.
Example:

  .profile | values

=item * C<paths()>

Enumerates every path within the current value, mirroring jq's C<paths>
helper. Each path is emitted as an array of keys and/or indices leading to
objects, arrays, and their nested scalars. Scalars (including booleans and
null) yield a single empty path, while empty arrays and objects contribute only
their immediate location.

Example:

  .user | paths

Returns:

  [["name"], ["tags"], ["tags",0], ["tags",1], ["active"]]

=item * C<leaf_paths()>

Enumerates only the paths that terminate in non-container values, mirroring
jq's C<leaf_paths> helper. This is equivalent to C<paths(scalars)> in jq.

Example:

  .user | leaf_paths

Returns:

  [["name"], ["tags",0], ["tags",1], ["active"]]

=item * C<getpath(path)>

Retrieves the value referenced by the supplied path array (or filter producing
path arrays), mirroring jq's C<getpath/1>. Literal JSON arrays can be passed
directly while expressions such as C<paths()> are evaluated against the current
input to collect candidate paths. When multiple paths are returned the helper
yields an array of values in the same order.

Examples:

  .profile | getpath(["name"])          # => "Alice"
  .profile | getpath(["emails", 1])     # => "alice.work\@example.com"
  .profile | getpath(paths())

=item * C<setpath(path; value)>

Sets or creates a value at the supplied path, following jq's C<setpath/2>
semantics. The first argument may be a literal JSON array or any filter that
emits path arrays. The second argument can be a literal (including JSON
objects/arrays) or another filter evaluated against the current input. Nested
hashes/arrays are automatically created as needed, and the original input is
never mutated.

Examples:

  .settings | setpath(["flags", "beta"]; true)
  .user     | setpath(["profile", "full_name"]; .name)


=item * C<pick(key1, key2, ...)>

Builds a new object containing only the supplied keys. When applied to arrays
of objects, each element is reduced to the requested subset while non-object
values pass through unchanged.

Example:

  .users | pick("name", "email")

Returns:

  [{ "name": "Alice", "email": "alice\@example.com" },
   { "name": "Bob" }]

=item * C<merge_objects()>

Merges arrays of objects into a single hash reference using last-write-wins
semantics. Non-object values within the array are ignored. When no objects are
found, an empty hash reference is returned. Applying the helper directly to an
object returns a shallow copy of that object.

Example:

  .items | merge_objects()

Returns:

  { "name": "Widget", "value": 2, "active": true }


=item * C<to_entries()>

Converts objects (and arrays) into an array of entry hashes, each consisting of
C<key> and C<value> fields in the jq style. Array entries use zero-based index
values for the key so they can be transformed uniformly.

Example:

  .profile | to_entries
  .tags    | to_entries


=item * C<from_entries()>

Performs the inverse of C<to_entries>. Accepts arrays containing
C<{ key => ..., value => ... }> hashes or C<[key, value]> tuples and rebuilds a
hash from them. Later entries overwrite earlier ones when duplicate keys are
encountered.

Example:

  .pairs | from_entries


=item * C<with_entries(filter)>

Transforms objects by mapping over their entries with the supplied filter,
mirroring jq's C<with_entries>. Each entry is exposed as a C<{ key, value }>
hash to the filter, and any entries filtered out are dropped prior to
reconstruction.

Example:

  .profile | with_entries(select(.key != "password"))


=item * C<map_values(filter)>

Applies the supplied filter to every value within an object, mirroring jq's
C<map_values>. When the filter returns no results for a key the entry is
removed, allowing constructs such as C<map_values(select(. > 0))> to prune
falsy values. Arrays are processed element-wise, so arrays of objects can be
transformed in a single step.

Example:

  .profile | map_values(tostring)

=back

=head1 BUILT-IN FILTER CATALOGUE

Beyond the helpers highlighted above, JQ::Lite ships with a wide set of jq
compatibility functions for arithmetic, statistics, type inspection, and data
manipulation. Highlights include:

=over 4

=item * Numeric helpers: C<add>, C<sum>, C<product>, C<min>, C<max>, C<avg>,
C<median>, C<percentile>, C<variance>, C<stddev>, C<clamp>

=item * Array utilities: C<first>, C<last>, C<reverse>, C<drop>, C<tail>,
C<chunks>, C<range>, C<transpose>, C<flatten_all>, C<flatten_depth>,
C<enumerate>

=item * Object introspection: C<keys>, C<keys_unsorted>, C<values>, C<has>,
C<contains>, C<to_entries>, C<from_entries>, C<with_entries>, C<map_values>,
C<pick>, C<merge_objects>, C<paths>, C<leaf_paths>, C<getpath>, C<setpath>,
C<del>, C<delpaths>

=item * Type and string tools: C<type>, C<tostring>, C<tojson>, C<fromjson>,
C<to_number>, C<upper>, C<lower>, C<titlecase>, C<trim>, C<ltrimstr>,
C<rtrimstr>, C<substr>, C<slice>, C<startswith>, C<endswith>, C<test>, C<split>,
C<join>, C<explode>, C<implode>

=item * Boolean logic and predicates: C<not>, C<any>, C<all>, C<walk>, C<map>,
C<select>, C<indices>, C<index>, C<rindex>, C<arrays>, C<objects>, C<scalars>

=back

Run C<jq-lite --help-functions> to view the exhaustive list directly from the
executable.

=head1 SEE ALSO

L<jq|https://stedolan.github.io/jq/>, L<JSON::PP>, L<JSON::XS>,
L<Cpanel::JSON::XS>, L<JSON::MaybeXS>

=head1 AUTHOR

Kawamura Shingo E<lt>pannakoota1\@gmail.comE<gt>

=head1 LICENSE

Same as Perl itself.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.