Skip to content

swaggerexpert/json-pointer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

@swaggerexpert/json-pointer

npmversion npm Test workflow Dependabot enabled try on RunKit Tidelift

@swaggerexpert/json-pointer is a parser, validator, evaluator, compiler and representer for RFC 6901 JavaScript Object Notation (JSON) Pointer.

Tidelift Get professionally supported @swaggerexpert/json-pointer with Tidelift Subscription.

Table of Contents

Getting started

Installation

You can install @swaggerexpert/json-pointer using npm:

 $ npm install @swaggerexpert/json-pointer

unpkg.com

Include following script tag into your HTML file:

<script src="https://unpkg.com/@swaggerexpert/json-pointer@latest/dist/json-pointer.browser.min.js"></script>

Global variable JSONPointer will be available in the browser matching interface of @swaggerexpert/json-pointer npm package. json-pointer.browser.min.js is UMD minified build artifact. There is also unminified json-pointer.browser.js artifact, suitable for debugging.


jsDelivr

Include following script tag into your HTML file (ESM mode):

<script type="module">
  import * as JSONPointer from 'https://cdn.jsdelivr.net/npm/@swaggerexpert/json-pointer@latest/+esm'
</script>

Local variable JSONPointer will be available in scope of the <script> tag matching interface of @swaggerexpert/json-pointer package.

Or include following script tag into your HTML file (UMD mode):

<script src="https://cdn.jsdelivr.net/npm/@swaggerexpert/json-pointer@latest/dist/json-pointer.browser.min.js"></script>

Global variable JSONPointer will be available in the browser matching interface of @swaggerexpert/json-pointer npm package. json-pointer.browser.min.js is UMD minified build artifact. There is also unminified json-pointer.browser.js artifact, suitable for debugging.

Usage

@swaggerexpert/json-pointer currently supports parsing, validation ,evaluation, compilation and representation. Both parser and validator are based on a superset of ABNF (SABNF) and use apg-lite parser generator.

Parsing

Parsing a JSON Pointer is as simple as importing the parse function and calling it.

import { parse } from '@swaggerexpert/json-parse';

const parseResult = parse('/foo/bar');

parseResult variable has the following shape:

{
  result: <ParseResult['result]>,
  tree: <ParseResult['tree']>,
  stats: <ParseResult['stats']>,
  trace: <ParseResult['trace']>,
}

TypeScript typings are available for all fields attached to parse result object returned by the parse function.

Translators

@swaggerexpert/json-pointer provides several translators to convert the parse result into different tree representations.

CST translator

Concrete Syntax Tree (Parse tree) representation is available on parse result when instance of CSTTranslator is provided via a translator option to the parse function. CST is suitable to be consumed by other tools like IDEs, editors, etc...

import { parse, CSTTranslator } from '@swaggerexpert/json-pointer';

const { tree: CST } = parse('/foo/bar', { translator: new CSTTranslator() });

CST tree has a shape documented by TypeScript typings (CSTTree).

AST translator

Default translator. Abstract Syntax Tree representation is available on parse result by default or when instance of ASTTranslator is provided via a translator option to the parse function. AST is suitable to be consumed by implementations that need to analyze the structure of the JSON Pointer or for building a custom JSON Pointer evaluation engine.

AST of the parsed JSON Pointer is a list of unescaped reference tokens.

import { parse } from '@swaggerexpert/json-pointer';

const { tree: AST } = parse('/foo/bar'); // AST = ['foo', 'bar']

or

import { parse, ASTTranslator } from '@swaggerexpert/json-poiner';

const { tree: AST } = parse('/foo/bar', { translator: new ASTTranslator() }); // AST = ['foo', 'bar']
XML translator
import { parse, XMLTranslator } from '@swaggerexpert/json-pointer';

const { tree: XML } = parse('$.store.book[0].title', { translator: new XMLTranslator() });
Statistics

parse function returns additional statistical information about the parsing process. Collection of the statistics can be enabled by setting stats option to true.

import { parse } from '@swaggerexpert/json-pointer';

const { stats } = parse('/foo/bar', { stats: true });

stats.displayStats(); // returns operator stats
stats.displayHits(); // returns rules grouped by hit count
Tracing

parse function returns additional tracing information about the parsing process. Tracing can be enabled by setting trace option to true. Tracing is essential for debugging failed matches or analyzing rule execution flow.

import { parse } from '@swaggerexpert/json-pointer';

const { result, trace } = parse('1', { trace: true });

result.success; // returns false
trace.displayTrace(); // returns trace information
trace.inferExpectations(); // returns parser expectations

By combining information from result and trace, it is possible to analyze the parsing process in detail and generate a messages like this: 'Invalid JSON Pointer: "1". Syntax error at position 0, expected "/"'. Please see this test file for more information how to achieve that.

Validation

Validating a JSON Pointer is as simple as importing one of the validation functions and calling it.

import {
  testJSONPointer,
  testReferenceToken,
  testArrayLocation,
  testArrayIndex,
  testArrayDash
} from '@swaggerexpert/json-pointer';

testJSONPointer('/foo/bar'); // => true
testReferenceToken('foo'); // => true
testArrayLocation('0'); // => true
testArrayLocation('-'); // => true
testArrayIndex('0'); // => true
testArrayDash('-'); // => true

Escaping

Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in JSON Pointer, '~' needs to be encoded as '~0' and '/' needs to be encoded as '~1' when these characters appear in a reference token.

import { escape } from '@swaggerexpert/json-pointer';

escape('~foo'); // => '~0foo'
escape('/foo'); // => '~1foo'

Unescape is performed by first transforming any occurrence of the sequence '~1' to '/', and then transforming any occurrence of the sequence '~0' to '~'. By performing the substitutions in this order, this library avoids the error of turning '~01' first into '~1' and then into '/', which would be incorrect (the string '~01' correctly becomes '~1' after transformation).

import { unescape } from '@swaggerexpert/json-pointer';

unescape('~0foo'); // => '~foo'
unescape('~1foo'); // => '/foo'

Evaluation

Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document and completes with a reference to some value within the document. Each reference token in the JSON Pointer is evaluated sequentially.

import { evaluate } from '@swaggerexpert/json-pointer';

const value = {
  "foo": ["bar", "baz"],
  "": 0,
  "a/b": 1,
  "c%d": 2,
  "e^f": 3,
  "g|h": 4,
  "i\\j": 5,
  "k\"l": 6,
  " ": 7,
  "m~n": 8
};

evaluate(value, ''); // => identical to value
evaluate(value, '/foo'); // => ["bar", "baz"]
evaluate(value, '/foo/0'); // => "bar"
evaluate(value, '/'); // => 0
evaluate(value, '/a~1b'); // => 1
evaluate(value, '/c%d'); // => 2
evaluate(value, '/e^f'); // => 3
evaluate(value, '/g|h'); // => 4
evaluate(value, '/i\\j'); // => 5
evaluate(value, '/k"l'); // => 6
evaluate(value, '/ '); // => 7
evaluate(value, '/m~0n'); // => 8

// neither object nor array
evaluate(null, '/foo'); // => throws JSONPointerTypeError
// arrays
evaluate(value, '/foo/2'); // => throws JSONPointerIndexError
evaluate(value, '/foo/-'); // => throws JSONPointerIndexError
evaluate(value, '/foo/a'); // => throws JSONPointerIndexError
// objects
evaluate(value, '/bar'); // => throws JSONPointerKeyError
Strict Arrays

By default, the evaluation is strict, meaning error condition will be raised if it fails to resolve a concrete value for any of the JSON pointer's reference tokens. For example, if an array is referenced with a non-numeric token, an error condition will be raised.

Note that the use of the "-" character to index an array will always result in such an error condition because by definition it refers to a nonexistent array element.

This spec compliant strict behavior can be disabled by setting the strictArrays option to false.

evaluate(value, '/foo/2', { strictArrays: false }); // => undefined
evaluate(value, '/foo/-', { strictArrays: false }); // => undefined
evaluate(value, '/foo/a', { strictArrays: false }); // => undefined
Strict Objects

By default, the evaluation is strict, meaning error condition will be raised if it fails to resolve a concrete value for any of the JSON pointer's reference tokens. For example, if a token references a key that is not present in an object, an error condition will be raised.

This spec compliant strict behavior can be disabled by setting the strictObjects option to false.

evaluate(value, '/bar', { strictObjects: false }); // => undefined

strictObjects options has no effect in cases where evaluation of previous reference token failed to resolve a concrete value.

evaluate(value, '/bar/baz', { strictObjects: false }); // => throw JSONPointerTypeError
Evaluation Realms

An evaluation realm defines the rules for interpreting and navigating data structures in JSON Pointer evaluation. While JSON Pointer traditionally operates on JSON objects and arrays, evaluation realms allow the evaluation to work polymorphically with different data structures, such as Map, Set, Immutable.js, or even custom representations like ApiDOM. Realm can be specified via the realm option in the evalute() function.

JSON Evaluation Realm

By default, the evaluation operates under the JSON realm, which assumes that:

  • Arrays are indexed numerically.
  • Objects (plain JavaScript objects) are accessed by string keys.

The default realm is represented by the JSONEvaluationRealm class.

import { evaluate } from '@swaggerexpert/json-pointer';

evaluate({ a: 'b' }, '/a'); // => 'b'

is equivalent to:

import { evaluate } from '@swaggerexpert/json-pointer';
import JSONEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/json';

evaluate({ a: 'b' }, '/a', { realm: new JSONEvaluationRealm() }); // => 'b'
Map/Set Evaluation Realm

The Map/Set realm extends JSON Pointer evaluation to support Map and Set instances, allowing structured traversal and access beyond traditional JavaScript objects and arrays. Map/Set realm is represented by the MapSetEvaluationRealm class.

import { evaluate } from '@swaggerexpert/json-pointer';
import MapSetEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/map-set';

const map = new Map([
  ['a', new Set(['b', 'c'])]
]);

evaluate(map, '/a/1', { realm: new MapSetEvaluationRealm() }); // => 'c'
Minim Evaluation Realm

The Minim Evaluation Realm extends JSON Pointer evaluation to support minim data structures, specifically ObjectElement, ArrayElement, and other element types from the minim.

Minim is widely used in API description languages (e.g., OpenAPI, API Blueprint, AsyncAPI and other API Description processing tools) to represent structured API data. The Minim Evaluation Realm enables seamless JSON Pointer traversal for these structures.

Before using the Minim Evaluation Realm, you need to install the minim package:

 $ npm install --save minim
import { ObjectElement } from 'minim';
import { evaluate } from '@swaggerexpert/json-pointer';
import MinimEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/minim';

const objectElement = new ObjectElement({
  a: ['b', 'c']
});

evaluate(objectElement, '/a/1', { realm: new MinimEvaluationRealm() }); // => StringElement('c')
ApiDOM Evaluation Realm

The ApiDOM Evaluation Realm is an integration layer that enables evaluation of JSON Pointer expressions on ApiDOM structures. It provides compatibility with ApiDOM core and namespace packages (@swagger-api/apidom-ns-*), allowing to traverse and query ApiDOM element instances.

Before using the ApiDOM Evaluation Realm, you need to install the @swagger-api/apidom-core package:

 $ npm install --save @swagger-api/apidom-core
import { ObjectElement } from '@swagger-api/apidom-core';
import { evaluate } from '@swaggerexpert/json-pointer';
import ApiDOMEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/apidom';

const objectElement = new ObjectElement({
  a: ['b', 'c']
});

evaluate(objectElement, '/a/1', { realm: new ApiDOMEvaluationRealm() }); // => StringElement('c')
Immutable.js Evaluation Realm

The Immutable.js Evaluation Realm is an integration layer that enables evaluation of JSON Pointer expressions on Immutable.js structures.

Before using the Immutable.js Evaluation Realm, you need to install the immutable package:

 $ npm install --save immutable
import { fromJS } from 'immutable';
import { evaluate } from '@swaggerexpert/json-pointer';
import ImmutableEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/immutable';

const map = fromJS({
  a: ['b', 'c']
});

evaluate(map, '/a/1', { realm: new ImmutableEvaluationRealm() }); // => 'c'
Custom Evaluation Realms

The evaluation is designed to support custom evaluation realms, enabling JSON Pointer evaluation for non-standard data structures.

A valid custom evaluation realm must match the structure of the EvaluationRealm interface.

One way to create a custom realm is to extend the EvaluationRealm class and implement the required methods.

import { evaluate, EvaluationRealm } from '@swaggerexpert/json-pointer';

class CustomEvaluationRealm extends EvaluationRealm {
  name = 'cusotm';

  isArray(node) { ... }
  isObject(node) { ... }
  sizeOf(node) { ... }
  has(node, referenceToken) { ... }
  evaluate(node, referenceToken) { ... }
}

evaluate({ a: 'b' }, '/a', { realm: new CustomEvaluationRealm() }); // => 'b'
Composing Evaluation Realms

Evaluation realms can be composed to create complex evaluation scenarios, allowing JSON Pointer evaluation to work across multiple data structures in a seamless manner. By combining different realms, composite evaluation ensures that a JSON Pointer query can resolve correctly whether the data structure is an object, array, Map, Set, or any custom type.

When composing multiple evaluation realms, the order matters. The composition is performed from left to right, meaning:

  • More specific realms should be placed first (leftmost position).
  • More generic realms should be placed later (rightmost position).

This ensures that specialized data structures (e.g., Map, Set, Immutable.js) take precedence over generic JavaScript objects and arrays.

import { composeRealms, evaluate } from '@swaggerexpert/json-pointer';
import JSONEvaluationRealm from '@swaggerexpert/json-pointer/realms/json';
import MapSetEvaluationRealm from '@swaggerexpert/json-pointer/realms/map-set';

const compositeRealm = composeRealms(new MapSetEvaluationRealm(), new JSONEvaluationRealm());

const structure = [
  {
    a: new Map([
      ['b', new Set(['c', 'd'])]
    ]),
  },
];

evaluate(structure, '/0/a/b/1', { realm : compositeRealm }); // => 'd'
Evaluation Diagnostics

@swaggerexpert/json-pointer provides rich diagnostic information to help identify and resolve issues during JSON Pointer evaluation.

When evaluation fails, the library throws errors from a well-defined hierarchy — all extending from JSONPointerEvaluateError. These errors carry detailed diagnostic metadata describing what failed, where it failed, and why.

Each error includes following fields:

  • jsonPointer – the full pointer being evaluated
  • referenceToken – the token that caused the failure
  • referenceTokenPosition – the index of that token within the pointer
  • referenceTokens – the full list of parsed reference tokens
  • currentValue – the value being evaluated at the point of failure
  • realm – the name of the evaluation realm (e.g., "json")
Evaluation Tracing

@swaggerexpert/json-pointer package supports evaluation tracing, allowing you to inspect each step of JSON Pointer evaluation in detail. This is especially useful for debugging, error reporting, visualization tools, or implementing custom behavior like fallbacks and partial evaluations.

How it works?

To enable tracing, provide an empty trace object when calling evaluate. trace object is populated with detailed information about each step of the evaluation process:

Tracing successful evaluation:

import { evaluate } from '@swaggerexpert/json-pointer';

const trace = {};
evaluate({ a: 'b' }, '/a', { trace });
// trace
{
  steps: [
    {
      referenceToken: 'a',
      referenceTokenPosition: 0,
      input: { a: 'b' },
      inputType: 'object',
      output: 'b',
      success: true
    }
  ],
  failed: false,
  failedAt: -1,
  message: 'JSON Pointer "/a" successfully evaluated against the provided value',
  context: {
    jsonPointer: '/a',
    referenceTokens: [ 'a' ],
    strictArrays: true,
    strictObjects: true,
    realm: 'json',
    value: { a: 'b' }
  }
}

Tracing failed evaluation:

import { evaluate } from '@swaggerexpert/json-pointer';

const trace = {};
try {
  evaluate({ a: 'b' }, '/c', { trace });
} catch {}
// trace
{
  steps: [
    {
      referenceToken: 'c',
      referenceTokenPosition: 0,
      input: { a: 'b' },
      inputType: 'object',
      output: undefined,
      success: false,
      reason: 'Invalid object key "c" at position 0 in "/c": key not found in object'
    }
  ],
  failed: true,
  failedAt: 0,
  message: 'Invalid object key "c" at position 0 in "/c": key not found in object',
  context: {
  jsonPointer: '/c',
    referenceTokens: [ 'c' ],
    strictArrays: true,
    strictObjects: true,
    realm: 'json',
    value: { a: 'b' }
  }
}

Compilation

Compilation is the process of transforming a list of unescaped reference tokens into a JSON Pointer. Reference tokens are escaped before compiled into a JSON Pointer.

import { compile } from '@swaggerexpert/json-pointer';

compile(['~foo', 'bar']); // => '/~0foo/bar'

Representation

JSON String

A JSON Pointer can be represented in a JSON string value. Per RFC4627, Section 2.5, all instances of quotation mark '"' (%x22), reverse solidus '\' (%x5C), and control (%x00-1F) characters MUST be escaped.

import { JSONString } from '@swaggerexpert/json-pointer';

JSONString.to('/foo"bar'); // => '"/foo\\"bar"'
JSONString.from('"/foo\\"bar"'); // => '/foo"bar'
URI Fragment Identifier

A JSON Pointer can be represented in a URI fragment identifier by encoding it into octets using UTF-8 RFC3629, while percent-encoding those characters not allowed by the fragment rule in RFC3986.

import { URIFragmentIdentifier } from '@swaggerexpert/json-pointer';

URIFragmentIdentifier.to('/foo"bar'); // => '#/foo%22bar'
URIFragmentIdentifier.from('#/foo%22bar'); // => '/foo"bar'

Errors

@swaggerexpert/json-pointer provides a structured error class hierarchy, enabling precise error handling across JSON Pointer operations, including parsing, evaluation, compilation and validation.

import {
  JSONPointerError,
  JSONPointerParseError,
  JSONPointerCompileError,
  JSONPointerEvaluateError,
  JSONPointerTypeError,
  JSONPointerKeyError,
  JSONPointerIndexError
} from '@swaggerexpert/json-pointer';

JSONPointerError is the base class for all JSON Pointer errors.

Grammar

New grammar instance can be created in following way:

import { Grammar } from '@swaggerexpert/json-pointer';

const grammar = new Grammar();

To obtain original ABNF (SABNF) grammar as a string:

import { Grammar } from '@swaggerexpert/json-pointer';

const grammar = new Grammar();

grammar.toString();
// or
String(grammar);

More about JSON Pointer

JSON Pointer is defined by the following ABNF syntax

; JavaScript Object Notation (JSON) Pointer ABNF syntax
; https://datatracker.ietf.org/doc/html/rfc6901
json-pointer    = *( slash reference-token ) ; MODIFICATION: surrogate text rule used
reference-token = *( unescaped / escaped )
unescaped       = %x00-2E / %x30-7D / %x7F-10FFFF
                ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
escaped         = "~" ( "0" / "1" )
                ; representing '~' and '/', respectively

; https://datatracker.ietf.org/doc/html/rfc6901#section-4
array-location  = array-index / array-dash
array-index     = %x30 / ( %x31-39 *(%x30-39) )
                ; "0", or digits without a leading "0"
array-dash      = "-"

; Surrogate named rules
slash           = "/"

License

@swaggerexpert/json-pointer is licensed under Apache 2.0 license. @swaggerexpert/json-pointer comes with an explicit NOTICE file containing additional legal notices and information.