Documentation
¶
Overview ¶
Package jsonpointer provides a golang implementation for json pointers.
Example (Iface) ¶
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
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) ¶
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
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 ¶
- Constants
- func Escape(token string) string
- func GetForToken(document any, decodedToken string, opts ...Option) (any, reflect.Kind, error)
- func SetDefaultNameProvider(provider NameProvider)
- func SetForToken(document any, decodedToken string, value any, opts ...Option) (any, error)
- func Unescape(token string) string
- func UseGoNameProvider()
- type JSONPointable
- type JSONSetable
- type NameProvider
- type Option
- type Pointer
- func (p *Pointer) DecodedTokens() []string
- func (p *Pointer) Get(document any, opts ...Option) (any, reflect.Kind, error)
- func (p *Pointer) IsEmpty() bool
- func (p *Pointer) Offset(document string) (int64, error)
- func (p *Pointer) Set(document any, value any, opts ...Option) (any, error)
- func (p *Pointer) String() string
Examples ¶
Constants ¶
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" // ErrDashToken indicates use of the RFC 6901 "-" reference token // in a context where it cannot be resolved. // // Per RFC 6901 §4 the "-" token refers to the (nonexistent) element // after the last array element. It may only be used as the terminal // token of a [Pointer.Set] against a slice, where it means "append". // Any other use (get, offset, intermediate traversal, non-slice target) // is an error condition that wraps this sentinel. ErrDashToken pointerError = `the "-" array token cannot be resolved here` //nolint:gosec // G101 false positive: this is a JSON Pointer reference token, not a credential. )
Variables ¶
This section is empty.
Functions ¶
func Escape ¶
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 ¶
GetForToken gets a value for a json pointer token 1 level deep.
func SetDefaultNameProvider ¶ added in v0.23.0
func SetDefaultNameProvider(provider NameProvider)
SetDefaultNameProvider sets the NameProvider as a package-level default.
By default, the default provider is jsonname.DefaultJSONNameProvider.
It is safe to call concurrently with Pointer.Get, Pointer.Set, GetForToken and SetForToken. The typical usage is to call it once at initialization time.
A nil provider is ignored.
func SetForToken ¶
SetForToken sets a value for a json pointer token 1 level deep.
See Pointer.Set for the mutation contract, in particular the handling of the RFC 6901 "-" token on slices.
func Unescape ¶
Unescape unescapes a json pointer reference token string to the original representation.
func UseGoNameProvider ¶ added in v0.23.0
func UseGoNameProvider()
UseGoNameProvider sets the NameProvider as a package-level default to the alternative provider jsonname.GoNameProvider, that covers a few areas not supported by the default name provider.
This implementation supports untagged exported fields and embedded types in go struct. It follows strictly the behavior of the JSON standard library regarding field naming conventions.
It is safe to call concurrently with Pointer.Get, Pointer.Set, GetForToken and SetForToken. The typical usage is to call it once at initialization time.
Example ¶
ExampleUseGoNameProvider contrasts the two NameProvider implementations shipped by github.com/go-openapi/swag/jsonname:
- the default provider requires a `json` struct tag to expose a field;
- the Go-name provider follows encoding/json conventions and accepts exported untagged fields and promoted embedded fields as well.
type Embedded struct {
Nested string // untagged: promoted only by the Go-name provider
}
type Doc struct {
Embedded // untagged embedded: promoted only by the Go-name provider
Tagged string `json:"tagged"`
Untagged string // no tag: visible only to the Go-name provider
}
doc := Doc{
Embedded: Embedded{Nested: "promoted"},
Tagged: "hit",
Untagged: "hidden-by-default",
}
for _, path := range []string{"/tagged", "/Untagged", "/Nested"} {
p, err := New(path)
if err != nil {
fmt.Println(err)
return
}
// Default provider: only the tagged field resolves.
defV, _, defErr := p.Get(doc)
// Go-name provider: untagged and promoted fields resolve too.
goV, _, goErr := p.Get(doc, WithNameProvider(jsonname.NewGoNameProvider()))
fmt.Printf("%s -> default=%v (err=%v) | goname=%v (err=%v)\n",
path, defV, defErr != nil, goV, goErr != nil)
}
Output: /tagged -> default=hit (err=false) | goname=hit (err=false) /Untagged -> default=<nil> (err=true) | goname=hidden-by-default (err=false) /Nested -> default=<nil> (err=true) | goname=promoted (err=false)
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.
//
// The key may be the RFC 6901 "-" token when the pointer targets a
// slice-like member; see the interface documentation for details.
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.
Handling of the RFC 6901 "-" token RFC 6901 "-" token" aria-label="Go to Handling of the RFC 6901 "-" token">¶
When a type implementing JSONSetable is the terminal parent of a Pointer.Set call, the library passes the raw reference token to JSONSet without interpretation. In particular, the RFC 6901 "-" token (which conventionally means "append" for arrays, per RFC 6902) is forwarded verbatim as the key argument. Implementations that model an array-like container are expected to give "-" the append semantics; implementations that do not should return an error wrapping ErrDashToken (or ErrPointer) for clarity.
Implementations are responsible for any in-place mutation: the library does not attempt to rebind the result of JSONSet into a parent container.
type NameProvider ¶ added in v0.23.0
type NameProvider interface {
// GetGoName gets the go name for a json property name
GetGoName(subject any, name string) (string, bool)
// GetGoNameForType gets the go name for a given type for a json property name
GetGoNameForType(tpe reflect.Type, name string) (string, bool)
}
NameProvider knows how to resolve go struct fields into json names.
The default provider is brought by github.com/go-openapi/swag/jsonname.DefaultJSONNameProvider.
func DefaultNameProvider ¶ added in v0.23.0
func DefaultNameProvider() NameProvider
DefaultNameProvider returns the current package-level NameProvider.
type Option ¶ added in v0.23.0
type Option func(*options)
Option to tune the behavior of a JSON Pointer.
func WithNameProvider ¶ added in v0.23.0
func WithNameProvider(provider NameProvider) Option
WithNameProvider injects a custom NameProvider to resolve json names from go struct types.
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 ¶
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 ¶
DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.
func (*Pointer) Get ¶
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 ¶
IsEmpty returns true if this is an empty json pointer.
This indicates that it points to the root document.
func (*Pointer) Set ¶
Set uses the pointer to set a value from a data type that represent a JSON document.
Mutation contract ¶
Set mutates the provided document in place whenever Go's type system allows it: when document is a map, a pointer, or when the targeted value is reached through an addressable ancestor (e.g. a struct field traversed via a pointer, a slice element). Callers that rely on this in-place behavior may continue to ignore the returned document.
The returned document is only load-bearing when Set cannot mutate in place. This happens in one specific case: appending to a top-level slice passed by value (e.g. document of type []T rather than *[]T) via the RFC 6901 "-" terminal token. reflect.Append produces a new slice header that the library cannot rebind into the caller's variable; the updated document is returned instead. Pass *[]T if you want in-place rebind for that case as well.
See ErrDashToken for the semantics of the "-" token.
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"}}
Example (Append) ¶
ExamplePointer_Set_append demonstrates the RFC 6901 "-" token as an append operation on a slice. On nested slices reached through an addressable parent (map entry, pointer to struct, ...), the append is performed in place and the returned document is the same reference.
doc := map[string]any{"foo": []any{"bar"}}
pointer, err := New("/foo/-")
if err != nil {
fmt.Println(err)
return
}
if _, err := pointer.Set(doc, "baz"); err != nil {
fmt.Println(err)
return
}
fmt.Printf("doc: %v\n", doc["foo"])
Output: doc: [bar baz]
Example (AppendTopLevelSlice) ¶
ExamplePointer_Set_appendTopLevelSlice shows the one case where the returned document is load-bearing: appending to a top-level slice passed by value. The library cannot rebind the slice header in the caller's variable, so callers must use the returned document (or pass *[]T to get in-place rebind).
doc := []int{1, 2}
pointer, err := New("/-")
if err != nil {
fmt.Println(err)
return
}
out, err := pointer.Set(doc, 3)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("original: %v\n", doc)
fmt.Printf("returned: %v\n", out)
Output: original: [1 2] returned: [1 2 3]