jsonpointer

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2025 License: MIT Imports: 5 Imported by: 7

README

JSON Pointer

Go Reference Go Report Card

Fast implementation of JSON Pointer (RFC 6901) specification in Go.

Installation

go get github.com/kaptinlin/jsonpointer

Usage

Basic Operations

Find a value in a JSON object using a JSON Pointer string:

package main

import (
    "fmt"
    "log"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    doc := map[string]any{
        "foo": map[string]any{
            "bar": 123,
        },
    }

    ref, err := jsonpointer.FindByPointer("/foo/bar", doc)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println(ref.Val) // 123
}
Find by Path Array

Use an array of steps to navigate to a value:

package main

import (
    "fmt"
    "log"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    doc := map[string]any{
        "foo": map[string]any{
            "bar": 123,
        },
    }

    path := jsonpointer.ParseJsonPointer("/foo/bar")
    ref, err := jsonpointer.Find(doc, path)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Value: %v, Object: %v, Key: %v\n", ref.Val, ref.Obj, ref.Key)
    // Value: 123, Object: map[bar:123], Key: bar
}
Safe Get Operations

Get values without error handling (returns nil if path doesn't exist):

package main

import (
    "fmt"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    doc := map[string]any{
        "users": []any{
            map[string]any{"name": "Alice", "age": 30},
            map[string]any{"name": "Bob", "age": 25},
        },
    }

    // Get existing value
    name := jsonpointer.Get(doc, jsonpointer.Path{"users", 0, "name"})
    fmt.Println(name) // Alice

    // Get non-existing value
    missing := jsonpointer.Get(doc, jsonpointer.Path{"users", 5, "name"})
    fmt.Println(missing) // <nil>
}
Path Manipulation

Convert between JSON Pointer strings and path arrays:

package main

import (
    "fmt"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    // Parse JSON Pointer string to path
    path := jsonpointer.ParseJsonPointer("/f~0o~1o/bar/1/baz")
    fmt.Printf("%+v\n", path)
    // [f~o/o bar 1 baz]

    // Format path array to JSON Pointer string
    pointer := jsonpointer.FormatJsonPointer(jsonpointer.Path{"f~o/o", "bar", "1", "baz"})
    fmt.Println(pointer)
    // /f~0o~1o/bar/1/baz
}
Component Encoding/Decoding

Encode and decode individual path components:

package main

import (
    "fmt"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    // Unescape component
    unescaped := jsonpointer.UnescapeComponent("~0~1")
    fmt.Println(unescaped) // ~/

    // Escape component
    escaped := jsonpointer.EscapeComponent("~/")
    fmt.Println(escaped) // ~0~1
}
Array Operations

Working with arrays and array indices:

package main

import (
    "fmt"
    "log"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    doc := map[string]any{
        "items": []any{1, 2, 3},
    }

    // Access array element
    ref, err := jsonpointer.Find(doc, jsonpointer.Path{"items", 1})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(ref.Val) // 2

    // Array end marker "-" points to next index
    ref, err = jsonpointer.Find(doc, jsonpointer.Path{"items", "-"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(ref.Key) // 3 (next available index)
}
Struct Operations

Working with Go structs and JSON tags:

package main

import (
    "fmt"
    "log"
    
    "github.com/kaptinlin/jsonpointer"
)

type User struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Email   string // No JSON tag, uses field name
    private string // Private field, ignored
    Ignored string `json:"-"` // Explicitly ignored
}

type Profile struct {
    User     *User  `json:"user"` // Pointer to struct
    Location string `json:"location"`
}

func main() {
    profile := Profile{
        User: &User{ // Pointer to struct
            Name:    "Alice",
            Age:     30,
            Email:   "alice@example.com",
            private: "secret",
            Ignored: "ignored",
        },
        Location: "New York",
    }

    // JSON tag access
    name := jsonpointer.Get(profile, jsonpointer.Path{"user", "name"})
    fmt.Println(name) // Alice

    // Field name access (no JSON tag)
    email := jsonpointer.Get(profile, jsonpointer.Path{"user", "Email"})
    fmt.Println(email) // alice@example.com

    // Private fields are ignored
    private := jsonpointer.Get(profile, jsonpointer.Path{"user", "private"})
    fmt.Println(private) // <nil>

    // json:"-" fields are ignored  
    ignored := jsonpointer.Get(profile, jsonpointer.Path{"user", "Ignored"})
    fmt.Println(ignored) // <nil>

    // Nested struct navigation
    age := jsonpointer.Get(profile, jsonpointer.Path{"user", "age"})
    fmt.Println(age) // 30

    // JSON Pointer syntax
    ref, err := jsonpointer.FindByPointer("/user/name", profile)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(ref.Val) // Alice

    // Mixed struct and map data
    data := map[string]any{
        "profile": profile,
        "meta":    map[string]any{"version": "1.0"},
        "users":   []User{{Name: "Bob", Age: 25}},
    }
    
    // Access struct in map
    location := jsonpointer.Get(data, jsonpointer.Path{"profile", "location"})
    fmt.Println(location) // New York
    
    // Access array of structs
    userName := jsonpointer.Get(data, jsonpointer.Path{"users", 0, "name"})
    fmt.Println(userName) // Bob
}
Validation

Validate JSON Pointer strings:

package main

import (
    "fmt"
    
    "github.com/kaptinlin/jsonpointer"
)

func main() {
    // Valid JSON Pointer
    err := jsonpointer.ValidateJsonPointer("/foo/bar")
    fmt.Println(err) // <nil>

    // Invalid JSON Pointer
    err = jsonpointer.ValidateJsonPointer("foo/bar") // missing leading slash
    fmt.Println(err) // error message
}

Acknowledgments

This project is a Go port of the excellent jsonjoy-com/json-pointer TypeScript implementation. We've adapted the core algorithms and added Go-specific performance optimizations while maintaining full RFC 6901 compatibility.

Special thanks to the original json-pointer project for providing a solid foundation and comprehensive test cases that enabled this high-quality Go implementation.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package jsonpointer provides JSON Pointer (RFC 6901) implementation for Go. This is a direct port of the TypeScript json-pointer library with identical behavior, using modern Go generics for type safety and performance.

This package implements helper functions for JSON Pointer (RFC 6901) specification. https://tools.ietf.org/html/rfc6901

TypeScript original source: https://github.com/jsonjoy-com/json-pointer

Usage:

import "github.com/kaptinlin/jsonpointer"

// Parse JSON Pointer string to path
path := jsonpointer.ParseJsonPointer("/users/0/name")

// Find value with error handling
ref, err := jsonpointer.Find(data, path)
if err != nil {
	// Handle error
}

// Get value without errors (returns nil for not found)
value := jsonpointer.Get(data, path)

// Validate JSON Pointer
err = jsonpointer.ValidateJsonPointer("/users/0/name")

Breaking Change Notice: This version is a complete rewrite using modern Go generics with zero backward compatibility. All function signatures use 'any' instead of 'interface{}' and follow TypeScript API exactly.

Package jsonpointer provides JSON Pointer (RFC 6901) implementation for Go. This is a direct port of the TypeScript json-pointer library with identical behavior, using modern Go generics for type safety and performance.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidIndex = errors.New("invalid index")

ErrInvalidIndex is returned when an invalid array index is encountered. TypeScript original code from find.ts: throw new Error('INVALID_INDEX');

View Source
var ErrInvalidPath = errors.New("invalid path")

ErrInvalidPath is returned when a path is not an array. TypeScript original code from validate.ts: if (!isArray(path)) throw new Error('Invalid path.');

View Source
var ErrInvalidPathStep = errors.New("invalid path step")

ErrInvalidPathStep is returned when a path step is not string or number. TypeScript original code from validate.ts: throw new Error('Invalid path step.');

View Source
var ErrNoParent = errors.New("no parent")

ErrNoParent is returned when trying to get parent of root path. TypeScript original code from util.ts: if (path.length < 1) throw new Error('NO_PARENT');

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned when a path cannot be traversed. TypeScript original code from find.ts: throw new Error('NOT_FOUND');

View Source
var ErrPathTooLong = errors.New("path too long")

ErrPathTooLong is returned when a path array exceeds maximum length. TypeScript original code from validate.ts: if (path.length > 256) throw new Error('Path too long.');

View Source
var ErrPointerInvalid = errors.New("pointer invalid")

ErrPointerInvalid is returned when a JSON Pointer string is invalid. TypeScript original code from validate.ts: if (pointer[0] !== '/') throw new Error('POINTER_INVALID');

View Source
var ErrPointerTooLong = errors.New("pointer too long")

ErrPointerTooLong is returned when a JSON Pointer string exceeds maximum length. TypeScript original code from validate.ts: if (pointer.length > 1024) throw new Error('POINTER_TOO_LONG');

Functions

func EscapeComponent

func EscapeComponent(component string) string

EscapeComponent escapes a JSON pointer path component. Returns the escaped component string.

TypeScript Original:

export function escapeComponent(component: string): string {
  if (component.indexOf('/') === -1 && component.indexOf('~') === -1) return component;
  return component.replace(r3, '~0').replace(r4, '~1');
}

func FormatJsonPointer

func FormatJsonPointer(path Path) string

FormatJsonPointer escapes and formats a path slice like []any{"foo", "bar"} to JSON pointer like "/foo/bar".

TypeScript Original:

export function formatJsonPointer(path: Path): string {
  if (isRoot(path)) return '';
  return '/' + path.map((component) => escapeComponent(String(component))).join('/');
}

func Get

func Get(val any, path Path) any

Get retrieves a value from object using path (never returns errors, returns nil for not found).

func IsArrayEnd

func IsArrayEnd[T any](ref ArrayReference[T]) bool

IsArrayEnd checks if an array reference points to the end of the array.

func IsArrayReference

func IsArrayReference(ref Reference) bool

IsArrayReference checks if a Reference points to an array element.

func IsChild

func IsChild(parent, child Path) bool

IsChild returns true if parent contains child path, false otherwise.

TypeScript Original:

export function isChild(parent: Path, child: Path): boolean {
  if (parent.length >= child.length) return false;
  for (let i = 0; i < parent.length; i++) if (parent[i] !== child[i]) return false;
  return true;
}

func IsInteger

func IsInteger(str string) bool

IsInteger checks if a string contains only digit characters (0-9).

TypeScript Original:

export const isInteger = (str: string): boolean => {
  const len = str.length;
  let i = 0;
  let charCode: any;
  while (i < len) {
    charCode = str.charCodeAt(i);
    if (charCode >= 48 && charCode <= 57) {
      i++;
      continue;
    }
    return false;
  }
  return true;
};

func IsObjectReference

func IsObjectReference(ref Reference) bool

IsObjectReference checks if a Reference points to an object property.

func IsPathEqual

func IsPathEqual(p1, p2 Path) bool

IsPathEqual returns true if two paths are equal, false otherwise.

TypeScript Original:

export function isPathEqual(p1: Path, p2: Path): boolean {
  if (p1.length !== p2.length) return false;
  for (let i = 0; i < p1.length; i++) if (p1[i] !== p2[i]) return false;
  return true;
}

func IsRoot

func IsRoot(path Path) bool

IsRoot returns true if JSON Pointer points to root value, false otherwise.

TypeScript Original: export const isRoot = (path: Path): boolean => !path.length;

func IsValidIndex

func IsValidIndex(index any) bool

IsValidIndex checks if path component can be a valid array index.

TypeScript Original:

export function isValidIndex(index: string | number): boolean {
  if (typeof index === 'number') return true;
  const n = Number.parseInt(index, 10);
  return String(n) === index && n >= 0;
}

func UnescapeComponent

func UnescapeComponent(component string) string

UnescapeComponent un-escapes a JSON pointer path component. Returns the unescaped component string.

TypeScript Original:

export function unescapeComponent(component: string): string {
  if (component.indexOf('~') === -1) return component;
  return component.replace(r1, '/').replace(r2, '~');
}

func ValidateJsonPointer

func ValidateJsonPointer(pointer any) error

ValidateJsonPointer validates a JSON Pointer string or Path.

func ValidatePath

func ValidatePath(path any) error

ValidatePath validates a path array.

Types

type ArrayReference

type ArrayReference[T any] struct {
	// Use pointer for undefined | T semantics (nil = undefined)
	Val *T  `json:"val"`
	Obj []T `json:"obj"`
	Key int `json:"key"`
}

ArrayReference represents a reference to an array element. TypeScript original code:

export interface ArrayReference<T = unknown> {
  readonly val: undefined | T;
  readonly obj: T[];
  readonly key: number;
}

type ObjectReference

type ObjectReference[T any] struct {
	Val T            `json:"val"`
	Obj map[string]T `json:"obj"`
	Key string       `json:"key"`
}

ObjectReference represents a reference to an object property. TypeScript original code:

export interface ObjectReference<T = unknown> {
  readonly val: T;
  readonly obj: Record<string, T>;
  readonly key: string;
}

type Path

type Path []PathStep

Path represents a JSON Pointer path as array of steps. TypeScript original code: export type Path = readonly PathStep[];

func Parent

func Parent(path Path) (Path, error)

Parent returns parent path, e.g. for []any{"foo", "bar", "baz"} returns []any{"foo", "bar"}. Returns ErrNoParent if the path has no parent (empty or root path).

TypeScript Original:

export function parent(path: Path): Path {
  if (path.length < 1) throw new Error('NO_PARENT');
  return path.slice(0, path.length - 1);
}

func ParseJsonPointer

func ParseJsonPointer(pointer string) Path

ParseJsonPointer converts JSON pointer like "/foo/bar" to path slice like []any{"foo", "bar"}, while also un-escaping reserved characters and precomputing array indices for performance.

TypeScript Original:

export function parseJsonPointer(pointer: string): Path {
  if (!pointer) return [];
  // TODO: Performance of this line can be improved: (1) don't use .split(); (2) don't use .map().
  return pointer.slice(1).split('/').map(unescapeComponent);
}

func ToPath

func ToPath(pointer any) Path

ToPath converts a pointer (string or Path) to Path. If the input is a string, it parses it as JSON pointer. If the input is already a Path, it returns it as-is.

TypeScript Original: export const toPath = (pointer: string | Path) => (typeof pointer === 'string' ? parseJsonPointer(pointer) : pointer);

type PathStep

type PathStep = any

PathStep represents a single step in a JSON Pointer path (string or number). TypeScript original code: export type PathStep = string | number;

type Reference

type Reference struct {
	Val any `json:"val"`
	Obj any `json:"obj,omitempty"`
	Key any `json:"key,omitempty"`
}

Reference represents a found reference with context. TypeScript original code:

export interface Reference {
  readonly val: unknown;
  readonly obj?: unknown | object | unknown[];
  readonly key?: string | number;
}

func Find

func Find(val any, path Path) (*Reference, error)

Find locates a reference in object using path (returns errors for invalid operations).

func FindByPointer

func FindByPointer(pointer string, val any) (*Reference, error)

FindByPointer optimized find operation using direct string parsing.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL