Skip to content

Hash "#" prefix? #84

Closed
Closed

Description

@rob-ross

Can you explain what the hash prefix does and how you intend it to work? I've got all but 3 tests in test_json_pointer.py passing. But I'm not exactly sure how the hash symbol is supposed to work. Can you explain this test case?

def test_non_standard_index_pointer() -> None:
    data = {"foo": {"bar": [1, 2, 3], "#baz": "hello"}}
    assert JSONPointer("/foo/bar/#1").resolve(data) == 1
    with pytest.raises(JSONPointerIndexError):
        JSONPointer("/foo/bar/#9").resolve(data)

Activity

  1. jg-rp commented on Aug 2, 2025

    @jg-rp
    Owner

    The hash prefix is there to point to an object (aka dict/map) key instead of the value associated with the key, or an array (aka list/sequence) index instead of the value at that index.

    The Relative JSON Pointer draft spec supports this kind of operation. By adding this # syntax to the JSON Pointer spec we guarantee that calling RelativeJSONPointer.to(some_json_pointer) will always return a usable instance of JSONPointer.

    Can you explain this test case?

    Here we're asserting that if data["foo"]["bar"] is an array with something at index 1, we just get the 1 back, regardless of the value at index 1. If we were to resolve "/foo/bar/1" (same but without the hash) against the same data, we'd get 2.

    data = {"foo": {"bar": [1, 2, 3], "#baz": "hello"}}
    assert JSONPointer("/foo/bar/#1").resolve(data) == 1

    And here we're checking that pointing to an out of bounds index raises an exception. This is the same behavior with or without a hash.

     with pytest.raises(JSONPointerIndexError):
            JSONPointer("/foo/bar/#9").resolve(data)

    Note that we have a similar situation with the non-standard keys_selector. It is there to ensure a JSONPathMatch can always be coerced to an instance of JSONPointer using class method JSONPointer.from_match().

    There's plenty of room for improvement in pointer.py. At the very least I'd like to separate non-standard behavior into one or more subclasses of JSONPointer, so users can choose to stick to RFC 6901.

  2. rob-ross commented on Aug 2, 2025

    @rob-ross
    ContributorAuthor

    So to summarize:

    Arrays:
    #n just returns n , as long as the array has an element at [n]
    Throws a JSONPointerIndexError if element [n] does not exist

    Objects:
    #foo returns foo if a ["foo"] key exists
    Throws a JSONPointerKeyError if key ["foo"] does not exist.

    Is this accurate?

    Also, this:

        assert JSONPointer("/foo/bar/01").resolve(data) == 1

    Is intended to fail with a
    JSONPointerTypeError: pointer type error 01: list indices must be integers or slices, not str

    But the following currently succeeds. Perhaps this is a bug?

        assert JSONPointer("/foo/bar/#01").resolve(data) == 1
  3. jg-rp commented on Aug 3, 2025

    @jg-rp
    Owner

    Yep, that's accurate. Unless an object has a key that actually starts with a hash. In which case we resolve the key to its associated value.

    data = {"foo": {"bar": [1, 2, 3], "#baz": "hello"}}
    assert JSONPointer("/foo/#baz").resolve(data) == "hello" # True

    But the following currently succeeds. Perhaps this is a bug?

    Yes, I'd call that a bug. We should follow the "digits without a leading '0'" policy from RFC 6901.

  4. Repository owner locked and limited conversation to collaborators on Aug 10, 2025
  5. converted this issue into a discussion #99 on Aug 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions