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 ¶
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 ¶
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 SetForToken ¶
SetForToken sets a value for a json pointer token 1 level deep.
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 ¶
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.
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"}}