JSPM

  • Created
  • Published
  • Downloads 707421
  • Score
    100M100P100Q205277F
  • License Apache-2.0

RCF 6901 implementation of JSON Pointer

Package Exports

  • @swaggerexpert/json-pointer
  • @swaggerexpert/json-pointer/package.json

Readme

@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

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: {
    success: true,
    state: 101,
    stateName: 'MATCH',
    length: 8,
    matched: 8,
    maxMatched: 8,
    maxTreeDepth: 8,
    nodeHits: 49
  },
  ast: fnast {
    callbacks: [
      'json-pointer': [Function: jsonPointer],
      'reference-token': [Function: referenceToken]
    ],
    init: [Function (anonymous)],
    ruleDefined: [Function (anonymous)],
    udtDefined: [Function (anonymous)],
    down: [Function (anonymous)],
    up: [Function (anonymous)],
    translate: [Function (anonymous)],
    setLength: [Function (anonymous)],
    getLength: [Function (anonymous)],
    toXml: [Function (anonymous)]
  },
  computed: [ 'foo', 'bar' ]
}
Evaluating AST as list of unescaped reference tokens

One of the ways to interpret the parsed JSON Pointer is to evaluate it as a list of unescaped reference tokens.

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

const { computed } = parse('/foo/bar'); // computed = ['foo', 'bar']
Interpreting AST as list of entries
import { parse } from '@swaggerexpert/json-parse';

const parseResult = parse('/foo/bar');
const parts = [];

parseResult.ast.translate(parts);

After running the above code, parts variable has the following shape:

[
  ['json-pointer', '/foo/bar'],
  ['reference-token', 'foo'],
  ['reference-token', 'bar'],
]
Interpreting AST as XML
import { parse } from '@swaggerexpert/json-pointer';

const parseResult = parse('/foo/bar');
const xml = parseResult.ast.toXml();

After running the above code, xml variable has the following content:

<?xml version="1.0" encoding="utf-8"?>
<root nodes="3" characters="8">
  <!-- input string -->
  /foo/bar
  <node name="json-pointer" index="0" length="8">
    /foo/bar
    <node name="reference-token" index="1" length="3">
      foo
    </node><!-- name="reference-token" -->
    <node name="reference-token" index="5" length="3">
      bar
    </node><!-- name="reference-token" -->
  </node><!-- name="json-pointer" -->
</root>

NOTE: AST can also be traversed in classical way using depth first traversal. For more information about this option please refer to apg-js and apg-js-examples.

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

Compilation

Compilation is the process of transforming a list of 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    = *( "/" reference-token )
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      = "-"

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.