JSON-Structure
view release on metacpan or search on metacpan
lib/JSON/Structure/JsonSourceLocator.pm view on Meta::CPAN
package JSON::Structure::JsonSourceLocator;
use strict;
use warnings;
use v5.20;
our $VERSION = '0.7.0';
use JSON::Structure::Types;
=head1 NAME
JSON::Structure::JsonSourceLocator - Track line and column positions in JSON documents
=head1 SYNOPSIS
use JSON::Structure::JsonSourceLocator;
my $locator = JSON::Structure::JsonSourceLocator->new($json_text);
my $location = $locator->get_location('#/properties/name');
if ($location->is_known) {
say "Found at line $location->{line}, column $location->{column}";
}
=head1 DESCRIPTION
This module tracks line and column positions in a JSON document and maps
JSON Pointer paths to source locations. It parses the JSON text to build
a map of paths to character offsets, then converts offsets to line/column
positions.
B<Limitations:> This is a lightweight, hand-rolled JSON path locator optimized
for typical JSON Structure schemas. It may report incorrect positions for:
=over 4
=item * Complex escape sequences in strings (e.g., C<\uXXXX> surrogate pairs)
=item * Deeply nested structures with many embedded strings containing braces/brackets
=item * Non-standard "relaxed" JSON (comments, trailing commas, unquoted keys)
=item * Very large documents where character-by-character parsing is slow
=back
For production use requiring precise positions in complex JSON, consider using
a streaming tokenizer like L<JSON::Streaming::Reader> or L<JSON::SL> that can
report byte offsets during parsing.
=cut
sub new {
my ( $class, $json_text ) = @_;
my $self = bless {
json_text => $json_text // '',
line_offsets => [],
}, $class;
$self->_build_line_offsets();
return $self;
}
=head2 get_location($path)
Returns a JsonLocation object for the given JSON Pointer path.
my $location = $locator->get_location('#/properties/name');
=cut
sub get_location {
my ( $self, $path ) = @_;
return JSON::Structure::Types::JsonLocation->unknown()
unless defined $path && length( $self->{json_text} );
# Parse the JSON Pointer path into segments
my @segments = $self->_parse_json_pointer($path);
# Find the location in the text
return $self->_find_location_in_text( \@segments );
}
sub _build_line_offsets {
my ($self) = @_;
my @offsets = (0); # First line starts at offset 0
my $text = $self->{json_text};
for ( my $i = 0 ; $i < length($text) ; $i++ ) {
if ( substr( $text, $i, 1 ) eq "\n" ) {
push @offsets, $i + 1;
}
}
$self->{line_offsets} = \@offsets;
}
sub _parse_json_pointer {
my ( $self, $path ) = @_;
# Remove leading # if present (JSON Pointer fragment identifier)
$path =~ s/^#//;
# Handle empty path or just "/"
( run in 1.632 second using v1.01-cache-2.11-cpan-140bd7fdf52 )