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 )