jsonpointer

package module
v0.22.3 Latest Latest
Warning

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

Go to latest
Published: Nov 17, 2025 License: Apache-2.0 Imports: 7 Imported by: 146

README

jsonpointer

Tests Coverage CI vuln scan CodeQL

Release Go Report Card CodeFactor Grade License

GoDoc go version Top language Commits since latest release


An implementation of JSON Pointer for golang, which supports go struct.

Status

API is stable.

Import this library in your project

go get github.com/go-openapi/jsonpointer

Basic usage

See also some examples

Retrieving a value
  import (
    "github.com/go-openapi/jsonpointer"
  )


  var doc any

  ...

	pointer, err := jsonpointer.New("/foo/1")
	if err != nil {
		... // error: e.g. invalid JSON pointer specification
	}

	value, kind, err := pointer.Get(doc)
	if err != nil {
		... // error: e.g. key not found, index out of bounds, etc.
	}

  ...
Setting a value
  ...
  var doc any
  ...
	pointer, err := jsonpointer.New("/foo/1")
	if err != nil {
		... // error: e.g. invalid JSON pointer specification
  }

	doc, err = p.Set(doc, "value")
	if err != nil {
		... // error: e.g. key not found, index out of bounds, etc.
	}

Change log

See https://github.com/go-openapi/jsonpointer/releases

References

https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07

also known as RFC6901

Licensing

This library ships under the SPDX-License-Identifier: Apache-2.0.

See the license NOTICE, which recalls the licensing terms of all the pieces of software on top of which it has been built.

Limitations

The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented.

That is because our implementation of the JSON pointer only supports explicit references to array elements: the provision in the spec to resolve non-existent members as "the last element in the array", using the special trailing character "-" is not implemented.

Other documentation

Documentation

Overview

Package jsonpointer provides a golang implementation for json pointers.

Example (Iface)
package main

import (
	"fmt"

	"github.com/go-openapi/jsonpointer"
)

var (
	_ jsonpointer.JSONPointable = CustomDoc{}
	_ jsonpointer.JSONSetable   = &CustomDoc{}
)

// CustomDoc accepts 2 preset properties "propA" and "propB", plus any number of extra properties.
//
// All values are strings.
type CustomDoc struct {
	a string
	b string
	c map[string]string
}

// JSONLookup implements [jsonpointer.JSONPointable].
func (d CustomDoc) JSONLookup(key string) (any, error) {
	switch key {
	case "propA":
		return d.a, nil
	case "propB":
		return d.b, nil
	default:
		if len(d.c) == 0 {
			return nil, fmt.Errorf("key %q not found: %w", key, ErrExampleIface)
		}
		extra, ok := d.c[key]
		if !ok {
			return nil, fmt.Errorf("key %q not found: %w", key, ErrExampleIface)
		}

		return extra, nil
	}
}

// JSONSet implements [jsonpointer.JSONSetable].
func (d *CustomDoc) JSONSet(key string, value any) error {
	asString, ok := value.(string)
	if !ok {
		return fmt.Errorf("a CustomDoc only access strings as values, but got %T: %w", value, ErrExampleIface)
	}

	switch key {
	case "propA":
		d.a = asString

		return nil
	case "propB":
		d.b = asString

		return nil
	default:
		if len(d.c) == 0 {
			d.c = make(map[string]string)
		}
		d.c[key] = asString

		return nil
	}
}

func main() {
	doc := CustomDoc{
		a: "initial value for a",
		b: "initial value for b",
		// no extra values
	}

	pointerA, err := jsonpointer.New("/propA")
	if err != nil {
		fmt.Println(err)

		return
	}

	// get the initial value for a
	propA, kind, err := pointerA.Get(doc)
	if err != nil {
		fmt.Println(err)

		return
	}
	fmt.Printf("propA (%v): %v\n", kind, propA)

	pointerB, err := jsonpointer.New("/propB")
	if err != nil {
		fmt.Println(err)

		return
	}

	// get the initial value for b
	propB, kind, err := pointerB.Get(doc)
	if err != nil {
		fmt.Println(err)

		return
	}
	fmt.Printf("propB (%v): %v\n", kind, propB)

	pointerC, err := jsonpointer.New("/extra")
	if err != nil {
		fmt.Println(err)

		return
	}

	// not found yet
	_, _, err = pointerC.Get(doc)
	fmt.Printf("propC: %v\n", err)

	_, err = pointerA.Set(&doc, "new value for a") // doc is updated in place
	if err != nil {
		fmt.Println(err)

		return
	}

	_, err = pointerB.Set(&doc, "new value for b")
	if err != nil {
		fmt.Println(err)

		return
	}

	_, err = pointerC.Set(&doc, "new extra value")
	if err != nil {
		fmt.Println(err)

		return
	}

	fmt.Printf("updated doc: %v", doc)

}
Output:

propA (string): initial value for a
propB (string): initial value for b
propC: key "extra" not found: example error
updated doc: {new value for a new value for b map[extra:new extra value]}
Example (Struct)
package main

import (
	"errors"
	"fmt"

	"github.com/go-openapi/jsonpointer"
)

var ErrExampleIface = errors.New("example error")

type ExampleDoc struct {
	PromotedDoc

	Promoted     EmbeddedDoc `json:"promoted"`
	AnonPromoted EmbeddedDoc `json:"-"`
	A            string      `json:"propA"`
	Ignored      string      `json:"-"`
	Untagged     string

	unexported string
}

type EmbeddedDoc struct {
	B string `json:"propB"`
}

type PromotedDoc struct {
	C string `json:"propC"`
}

func main() {
	doc := ExampleDoc{
		PromotedDoc: PromotedDoc{
			C: "c",
		},
		Promoted: EmbeddedDoc{
			B: "promoted",
		},
		A:          "a",
		Ignored:    "ignored",
		unexported: "unexported",
	}

	{
		// tagged simple field
		pointerA, _ := jsonpointer.New("/propA")
		a, _, err := pointerA.Get(doc)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("a: %v\n", a)
	}

	{
		// tagged struct field is resolved
		pointerB, _ := jsonpointer.New("/promoted/propB")
		b, _, err := pointerB.Get(doc)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("b: %v\n", b)
	}

	{
		// tagged embedded field is resolved
		pointerC, _ := jsonpointer.New("/propC")
		c, _, err := pointerC.Get(doc)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("c: %v\n", c)
	}

	{
		// exlicitly ignored by JSON tag.
		pointerI, _ := jsonpointer.New("/ignored")
		_, _, err := pointerI.Get(doc)
		fmt.Printf("ignored: %v\n", err)
	}

	{
		// unexported field is ignored: use [JSONPointable] to alter this behavior.
		pointerX, _ := jsonpointer.New("/unexported")
		_, _, err := pointerX.Get(doc)
		fmt.Printf("unexported: %v\n", err)
	}

	{
		// Limitation: anonymous field is not resolved.
		pointerC, _ := jsonpointer.New("/propB")
		_, _, err := pointerC.Get(doc)
		fmt.Printf("anonymous: %v\n", err)
	}

	{
		// Limitation: untagged exported field is ignored, unlike with json standard MarshalJSON.
		pointerU, _ := jsonpointer.New("/untagged")
		_, _, err := pointerU.Get(doc)
		fmt.Printf("untagged: %v\n", err)
	}

}
Output:

a: a
b: promoted
c: c
ignored: object has no field "ignored": JSON pointer error
unexported: object has no field "unexported": JSON pointer error
anonymous: object has no field "propB": JSON pointer error
untagged: object has no field "untagged": JSON pointer error

Index

Examples

Constants

View Source
const (
	// ErrPointer is a sentinel error raised by all errors from this package.
	ErrPointer pointerError = "JSON pointer error"

	// ErrInvalidStart states that a JSON pointer must start with a separator ("/").
	ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator

	// ErrUnsupportedValueType indicates that a value of the wrong type is being set.
	ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values"
)

Variables

This section is empty.

Functions

func Escape

func Escape(token string) string

Escape escapes a pointer reference token string.

The JSONPointer specification defines "/" as a separator and "~" as an escape prefix.

Keys containing such characters are escaped with the following rules:

  • "~" is escaped as "~0"
  • "/" is escaped as "~1"

func GetForToken

func GetForToken(document any, decodedToken string) (any, reflect.Kind, error)

GetForToken gets a value for a json pointer token 1 level deep.

func SetForToken

func SetForToken(document any, decodedToken string, value any) (any, error)

SetForToken sets a value for a json pointer token 1 level deep.

func Unescape

func Unescape(token string) string

Unescape unescapes a json pointer reference token string to the original representation.

Types

type JSONPointable

type JSONPointable interface {
	// JSONLookup returns a value pointed at this (unescaped) key.
	JSONLookup(key string) (any, error)
}

JSONPointable is an interface for structs to implement, when they need to customize the json pointer process or want to avoid the use of reflection.

type JSONSetable

type JSONSetable interface {
	// JSONSet sets the value pointed at the (unescaped) key.
	JSONSet(key string, value any) error
}

JSONSetable is an interface for structs to implement, when they need to customize the json pointer process or want to avoid the use of reflection.

type Pointer

type Pointer struct {
	// contains filtered or unexported fields
}

Pointer is a representation of a json pointer.

Use Pointer.Get to retrieve a value or Pointer.Set to set a value.

It works with any go type interpreted as a JSON document, which means:

  • if a type implements JSONPointable, its [JSONPointable.JSONLookup] method is used to resolve Pointer.Get
  • if a type implements JSONSetable, its [JSONPointable.JSONSet] method is used to resolve Pointer.Set
  • a go map[K]V is interpreted as an object, with type K assignable to a string
  • a go slice []T is interpreted as an array
  • a go struct is interpreted as an object, with exported fields interpreted as keys
  • promoted fields from an embedded struct are traversed
  • scalars (e.g. int, float64 ...), channels, functions and go arrays cannot be traversed

For struct s resolved by reflection, key mappings honor the conventional struct tag `json`.

Fields that do not specify a `json` tag, or specify an empty one, or are tagged as `json:"-"` are ignored.

Limitations

  • Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored.
  • anonymous fields are not traversed if untagged

func New

func New(jsonPointerString string) (Pointer, error)

New creates a new json pointer from its string representation.

Example
empty, err := New("")
if err != nil {
	fmt.Println(err)

	return
}
fmt.Printf("empty pointer: %q\n", empty.String())

key, err := New("/foo")
if err != nil {
	fmt.Println(err)

	return
}
fmt.Printf("pointer to object key: %q\n", key.String())

elem, err := New("/foo/1")
if err != nil {
	fmt.Println(err)

	return
}
fmt.Printf("pointer to array element: %q\n", elem.String())

escaped0, err := New("/foo~0")
if err != nil {
	fmt.Println(err)

	return
}
// key contains "~"
fmt.Printf("pointer to key %q: %q\n", Unescape("foo~0"), escaped0.String())

escaped1, err := New("/foo~1")
if err != nil {
	fmt.Println(err)

	return
}
// key contains "/"
fmt.Printf("pointer to key %q: %q\n", Unescape("foo~1"), escaped1.String())
Output:

empty pointer: ""
pointer to object key: "/foo"
pointer to array element: "/foo/1"
pointer to key "foo~": "/foo~0"
pointer to key "foo/": "/foo~1"

func (*Pointer) DecodedTokens

func (p *Pointer) DecodedTokens() []string

DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.

func (*Pointer) Get

func (p *Pointer) Get(document any) (any, reflect.Kind, error)

Get uses the pointer to retrieve a value from a JSON document.

It returns the value with its type as a reflect.Kind or an error.

Example
var doc exampleDocument

if err := json.Unmarshal(testDocumentJSONBytes, &doc); err != nil { // populates doc
	fmt.Println(err)

	return
}

pointer, err := New("/foo/1")
if err != nil {
	fmt.Println(err)

	return
}

value, kind, err := pointer.Get(doc)
if err != nil {
	fmt.Println(err)

	return
}

fmt.Printf(
	"value: %q\nkind: %v\n",
	value, kind,
)
Output:

value: "baz"
kind: string

func (*Pointer) IsEmpty

func (p *Pointer) IsEmpty() bool

IsEmpty returns true if this is an empty json pointer.

This indicates that it points to the root document.

func (*Pointer) Offset added in v0.20.0

func (p *Pointer) Offset(document string) (int64, error)

func (*Pointer) Set

func (p *Pointer) Set(document any, value any) (any, error)

Set uses the pointer to set a value from a data type that represent a JSON document.

It returns the updated document.

Example
var doc exampleDocument

if err := json.Unmarshal(testDocumentJSONBytes, &doc); err != nil { // populates doc
	fmt.Println(err)

	return
}

pointer, err := New("/foo/1")
if err != nil {
	fmt.Println(err)

	return
}

result, err := pointer.Set(&doc, "hey my")
if err != nil {
	fmt.Println(err)

	return
}

fmt.Printf("result: %#v\n", result)
fmt.Printf("doc: %#v\n", doc)
Output:

result: &jsonpointer.exampleDocument{Foo:[]string{"bar", "hey my"}}
doc: jsonpointer.exampleDocument{Foo:[]string{"bar", "hey my"}}

func (*Pointer) String

func (p *Pointer) String() string

String representation of a pointer.

Jump to

Keyboard shortcuts

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