jsonpointer

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2022 License: Apache-2.0 Imports: 13 Imported by: 1

README

jsonpointer - an RFC 6901 implementation for Go

GoDoc

Package jsonpointer provides the ability to resolve, assign, and delete values of any type, including raw JSON, by JSON Pointers.

Install

go get github.com/chanced/jsonpointer

Usage

General
package main

import (
    "log"
    "encoding/json"
    "github.com/chanced/jsonpointer"
)

type Nested struct {
    Str string
}

type Root struct {
    Nested Nested
}

func main() {

    r := Root{ Nested: Nested{ Str: "nested str" }}

    // jsonpointer.Pointer is a string type so if you have a properly
    // formatted json pointer then you can simply convert it:
    //  ptr := jsonpointer.Pointer(myPointer)
    //  err := ptr.Validate()

    // Note: jsonpointer.New encodes each token's value.
    // "/" encodes to "~1" and "~" encodes to "~0" in compliance with RFC 6901.

    ptr := jsonpointer.New("nested", "str")

    // Resolve

    var s string
    err := jsonpointer.Resolve(r, ptr, &s)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(s) // outputs "nested str"

    // Assign

    err = jsonpointer.Assign(&r, ptr, "new value")
    if err != nil {
        log.Fatal(err)
    }
    log.Println(r.Nested.Str) // outputs "new value"


    // Delete

    err = jsonpointer.Delete(&r, ptr)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(r.Nested.Str) // outputs ""


    // jsonpointer can also Resolve, Assign, and Delete JSON in []byte format.
    // This includes field values, such as those of type json.RawMessage.

    r.Nested.Str = "str val"

    b, err := json.Marshal(r)
    if err != nil {
        log.Fatal(err)
    }

    err = jsonpointer.Resolve(b, ptr, &s)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(s) // outputs "str val"
}
Interfaces

Package jsonpointer provides 3 interfaces: Assigner, Resolver, and Deleter. Regardless of the operation, if Resolver is implemented, ResolvePointer will be called. ResolvePointer should not have side effects. If resolving for an assignment, utilize the pointer to infer which type should be assigned.

AssignByPointer is invoked on the way back from the leaf. DeleteByPointer is invoked after resolving the current token.

All three methods are passed a pointer to the jsonpointer.Pointer so that it can be modified. If you do not modify it, jsonpointer will assume the current token was addressed and continue on.

If you wish to only handle some cases with the interfaces, return jsonpointer.YieldOperation to have the jsonpointer package resolve, assign, or delete as if the type did not implement the interface. Note that doing so results in changes to ptr being dismissed.

Pointer methods

All methods return new values rather than modifying the pointer itself. If you wish to modify the pointer in one of the interface methods, you will need to reassign it: *ptr = newPtrVal

func (mt MyType) ResolvePointer(ptr *jsonpointer.Pointer, op Operation) (interface{}, error) {
    next, t, ok := ptr.Next()
    if !ok {
        // this will only occur if the ptr is a root token in this circumstance
        return mt
    }
    if op == jsonpointer.Assigning && t == "someInterface" {
        // maybe you need to know what comes after someInterface to
        // determine what implementation of someInterface to assign
        t, _ = next.NextToken()

        switch t {
        case "someIdentifier":
            // you could modify ptr if you felt so inclined: *ptr = next
            // but it is not needed in this scenario.
            return SomeImplementation{}, nil
        }
    }
    // otherwise hand resolution back over to jsonpointer
    return nil, jsonpointer.YieldOperation
}

Errors

All errors returned from Resolve, Assign, and Delete will implement Error. A convenience function AsError exists to help extract out the details.

Depending on the cause, the error could also be KeyError, IndexError, FieldError with additional details. All have corresponding As{Error} functions.

Finally, all errors have associated Err instances that are wrapped, such as ErrMalformedToken, ErrInvalidKeyType, and so on.

See errors.go for further details on errors.

Contributions & Issues

Contributions are always welcome. If you run into an issue, please open a issue on github. If you would like to submit a change, feel free to open up a pull request.

Note on Performance

This package is reflect heavy. While it employs the same caching mechanics as encoding/json to help alleviate some of the lookup costs, there will always be a performance hit with reflection.

There are probably plenty of ways to improve performance of the package. Improvements or criticisms are always welcome.

With regards to raw JSON, json.Marshal and json.Unmarshal are utilized. Ideally, in the future, that will change and the package will incoroprate the encoding/decoding logic from encoding/json directly, thus skipping the need to run through unneccesary logic.

Alternative JSON Pointer Packages for Go

License

Apache 2.0

Documentation

Overview

Package jsonpointer provides the ability to resolve, assign, and delete values of any type, including raw JSON, by [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901).

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrMalformedToken is returned when a JSONPointer token is malformed.
	//
	// This error is returned by JSONPointer.Validate() which is called by
	// Resolve, Assign, and Delete.
	//
	ErrMalformedToken = errors.New(`jsonpointer: fragment is malformed`)

	// ErrMalformedStart is an ErrMalformedToken that is returned when the JSON
	// Pointer is not empty or does not start with a "/".
	ErrMalformedStart = fmt.Errorf(`%w; pointer must be an empty string or start with "/"`, ErrMalformedToken)

	ErrMalformedEncoding = fmt.Errorf("%w; '~' must be encoded as ~0", ErrMalformedToken)

	// ErrNonPointer indicates a non-pointer value was passed to Assign.
	//
	ErrNonPointer = errors.New("jsonpointer: dst must be a pointer")

	// ErrUnexportedField indicates the given path is not reachable due to being
	// an unexported field.
	//
	ErrUnexportedField = errors.New("jsonpointer: unexported field")

	// ErrInvalidKeyType indicates the key type is not supported.
	//
	// Custom key types must implement encoding.TextUnmarshaler
	//
	ErrInvalidKeyType = errors.New("jsonpointer: invalid key type")

	// ErrNotAssignable indicates the type of the value is not assignable to the
	// provided path.
	//
	ErrNotAssignable = errors.New("jsonpointer: invalid value type")

	// ErrNotFound indicates a JSONPointer is not reachable from the root object
	// (e.g. a nil pointer, missing map key).
	//
	ErrNotFound = errors.New(`jsonpointer: value not found`)

	// ErrOutOfRange indicates an index is out of range for an array or slice
	//
	ErrOutOfRange = errors.New("jsonpointer: index out of range")

	// ErrUnreachable indicates a reference is not reachable. This occurs when
	// resolving and a primitive (string, number, or bool) leaf node is reached
	// and the reference is not empty.
	//
	ErrUnreachable = fmt.Errorf("%w due to being unreachable", ErrNotFound)

	// ErrNilInterface is returned when assigning and a nil interface is
	// reached.
	//
	// To solve this, the node containing the interface should implement
	// jsonpoint.Resolver and return a non-nil implemention of the interface.
	//
	ErrNilInterface = errors.New("jsonpointer: can not assign due to nil interface")

	// ErrMalformedIndex indicates a syntax error in the index or a slice or an array.
	ErrMalformedIndex = errors.New("jsonpointer: malformed slice or array index")
)
View Source
var YieldOperation = errors.New("yield resolution to jsonpointer")

YieldOperation returns resolution back to jsonpointer. This error can be utilized within methods satisfying Resolver (ResolveJSONPointer), Assigner (AssignByJSONPointer), and Deleter (DeleteByJSONPointer) as an escape hatch.

The intent is is that there may only be certain fields that your application would like to manually resolve. For the rest, you'd return YieldOperation as the error.

Functions

func Assign

func Assign(dst interface{}, ptr Pointer, value interface{}) error

Assign performs an assignment of value to the target dst specified by the JSON Pointer ptr. Assign traverses dst recursively, resolving the path of the JSON Pointer. If a type in the path implements Resolver, it will attempt to resolve by invoking ResolveJSONPointer on that value. If ResolveJSONPointer returns YieldOperation or if the value does not implement ResolveJSONPointer, encoding/json naming conventions are utilized to resolve the path.

If a type in the path implements Assigner, AssignByJSONPointer will be called with the updated value pertinent to that path.

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/chanced/jsonpointer"
)

func main() {
	type Bar struct {
		Baz string `json:"baz"`
	}
	type Foo struct {
		Bar Bar `json:"bar"`
	}
	var foo Foo
	jsonpointer.Assign(&foo, "/bar/baz", "qux")
	fmt.Println(foo.Bar.Baz)

	// Assigning JSON by JSONPointer

	foo.Bar.Baz = "quux"
	b, _ := json.Marshal(foo)
	jsonpointer.Assign(&b, "/bar/baz", "corge")
	fmt.Println(string(b))

}
Output:

qux
{"bar":{"baz":"corge"}}

func Decode

func Decode(token string) string

Decode decodes a JSON Pointer token by replacing each encoded slash ("~1") with '/' (%x2F) and each encoded tilde ("~0") with '~' (%x7E).

func Delete

func Delete(src interface{}, ptr Pointer) error

Delete deletes the value at the given JSON pointer from src.

If any part of the path is unreachable, the Delete function is considered a success as the value is not present to delete.

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/chanced/jsonpointer"
)

func main() {
	type Bar struct {
		Baz string `json:"baz,omitempty"`
	}
	type Foo struct {
		Bar Bar `json:"bar"`
	}
	foo := Foo{Bar{Baz: "qux"}}

	jsonpointer.Delete(foo, "/bar/baz")
	fmt.Printf("foo.Bar.Baz: %v\n", foo.Bar.Baz)

	// Deleting JSON by JSONPointer
	foo.Bar.Baz = "quux"
	b, _ := json.Marshal(foo)
	jsonpointer.Delete(&b, "/bar/baz")
	fmt.Println(string(b))

}
Output:

foo.Bar.Baz: qux
{"bar":{}}

func Encode

func Encode(token string) string

Encode encodes a string to a token of a JSON Pointer by replacing each '~' (%x7E) with "~0" and '/' (%x2F) with "~1".

func IsKeyError

func IsKeyError(err error) bool

func IsValueError

func IsValueError(err error) bool

func Resolve

func Resolve(src interface{}, ptr Pointer, dst interface{}) error

Resolve performs resolution on src by traversing the path of the JSON Pointer and assigning the value to dst. If the path can not be reached, an error is returned.

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/chanced/jsonpointer"
)

func main() {
	type Bar struct {
		Baz string `json:"baz"`
	}
	type Foo struct {
		Bar Bar `json:"bar,omitempty"`
	}
	foo := Foo{Bar{Baz: "qux"}}

	var s string
	jsonpointer.Resolve(foo, "/bar/baz", &s)
	fmt.Println(s)

	// Resolving JSON by JSONPointer

	b, _ := json.Marshal(foo)
	jsonpointer.Resolve(b, "/bar/baz", &s)
	fmt.Println(s)

}
Output:

qux
qux

Types

type Assigner

type Assigner interface {
	AssignByJSONPointer(ptr *Pointer, value interface{}) error
}

Assigner is the interface implemented by types which can assign values via JSON Pointers. The input can be assumed to be a valid JSON Pointer and the value to assign.

AssignByJSONPointer is called after the value has been resolved. If custom resolution is needed, the type should also implement Resolver.

type Deleter

type Deleter interface {
	DeleteByJSONPointer(ptr *Pointer) error
}

Deleter is an interface that is implemented by any type which can delete a value by JSON pointer.

type Error

type Error interface {
	error
	JSONPointer() Pointer
	CurrentJSONPointer() Pointer
	Token() (Token, bool)
	Operation() Operation
	Unwrap() error
	Type() reflect.Type
}

Error is a base error type returned from Resolve, Assign, and Delete.

func AsError

func AsError(err error) (Error, bool)

type FieldError

type FieldError interface {
	Error
	Field() reflect.StructField
}

FieldError indicates an error occurred with regards to a field of a struct.

type IndexError

type IndexError interface {
	MaxIndex() int
	Index() int
	Error() string
	Unwrap() error
}

IndexError indicates an error occurred with regards to an index of a slice or array. The error may be wrapped in an Error if it is returned from an operation on a JSON Pointer.

err.Index() will return -1 if:

- the source or destination is an array, token is equal to "-", and the array does not have a zero value.

- the token can not be parsed as an int

func AsIndexError

func AsIndexError(err error) (IndexError, bool)

AsIndexError returns err as a IndexError, if possible. It does so by calling errors.As, returning a IndexError and true if successful. If unsuccessful, nil and false is returned.

type JSONPointer

type JSONPointer = Pointer

Alias for Pointer Deprecated in favor of Pointer

type KeyError

type KeyError interface {
	Error
	KeyType() reflect.Type
	KeyValue() interface{}
}

KeyError indicates an error occurred with regards to the key of a map or slice.

func AsKeyError

func AsKeyError(err error) (KeyError, bool)

AsKeyError returns err as a ValueError, if possible. It does so by calling errors.As, returning a KeyError and true if successful. If unsuccessful, nil and false is returned.

type Operation

type Operation uint8

Operation is the type of operation being performed.

const (
	// Resolving is the operation for resolving a JSON pointer.
	Resolving Operation = iota
	// Deleting is the operation for deleting by a JSON pointer.
	Deleting
	// Assigning is the operation for assigning by a JSON pointer.
	Assigning
)

func (Operation) IsAssigning

func (o Operation) IsAssigning() bool

IsAssigning returns true if the operation is an assignment.

func (Operation) IsDeleting

func (o Operation) IsDeleting() bool

IsDeleting returns true if the operation is a deletion.

func (Operation) IsResolving

func (o Operation) IsResolving() bool

IsResolving returns true if the operation is a resolution.

func (Operation) String

func (o Operation) String() string

type Pointer added in v0.0.3

type Pointer string

A Pointer is a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a '/' character.

See [rfc 6901 for more information](https://datatracker.ietf.org/doc/html/rfc6901).

const (
	// Root is a top-level JSONPointer, indicated by "/".
	Root Pointer = ""
)

func From deprecated added in v0.0.4

func From(ptr string) (Pointer, error)

Deprecated: use Parse instead.

func New

func New(tokens ...string) Pointer

New encodes and returns the token + tokens into a JSONPointer.

Examples:

jsonpointer.New("foo", "bar") => "/foo/bar"
jsonpointer.New("/foo/bar") => "/~1foo~1bar"
jsonpointer.New() => "/"
jsonpointer.New("") => "/"
jsonpointer.New("/") => "/~1"
jsonpointer.New("~") => "/~0"
Example
package main

import (
	"fmt"

	"github.com/chanced/jsonpointer"
)

func main() {
	ptr := jsonpointer.New("foo", "bar") // => "/foo/bar"
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New("foo/bar") // => "/foo~1bar"
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New() // => ""
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New("") // => "/"
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New("/") // => "/~1"
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New("~") // => "/~0"
	fmt.Println(`"` + ptr + `"`)

	ptr = jsonpointer.New("#/foo/bar") // => "/#~1foo~1bar"
	fmt.Println(`"` + ptr + `"`)

}
Output:

"/foo/bar"
"/foo~1bar"
""
"/"
"/~1"
"/~0"
"/#~1foo~1bar"

func NewFromStrings

func NewFromStrings(tokens []string) Pointer

NewFromStrings encodes and returns the tokens into a JSONPointer.

Examples:

jsonpointer.NewFromStrings([]string{"foo", "bar", "baz"}) => "/foo/bar/baz"

func Parse added in v0.0.5

func Parse(ptr string) (Pointer, error)

Parse accepts a string, trims any leading '#' and returns str as a Pointer as well as any validation errors.

func (Pointer) Append added in v0.0.3

func (p Pointer) Append(token Token) Pointer

Append appends token to the end of reference p and returns the new JSONPointer.

Note: token is not encoded. Use p.AppendString to encode and append the token.

func (Pointer) AppendString added in v0.0.3

func (p Pointer) AppendString(token string) Pointer

AppendString encodes and appends token to the value of p and returns the new JSONPointer.

func (Pointer) IsRoot added in v0.0.3

func (p Pointer) IsRoot() bool

func (Pointer) LastToken added in v0.0.3

func (p Pointer) LastToken() (Token, bool)

func (Pointer) Next added in v0.0.3

func (p Pointer) Next() (Pointer, Token, bool)

Next splits the JSONPointer at the first slash and returns the token and the remaining JSONPointer.

func (Pointer) NextPointer added in v0.0.3

func (p Pointer) NextPointer() (Pointer, bool)

func (Pointer) NextToken added in v0.0.3

func (p Pointer) NextToken() (Token, bool)

NextToken splits the JSONPointer at the first slash and returns the token.

func (Pointer) Pop added in v0.0.3

func (p Pointer) Pop() (Pointer, Token, bool)

func (Pointer) Prepend added in v0.0.3

func (p Pointer) Prepend(token Token) Pointer

Preppend prepends token to the beginning of the value of p and returns the resulting JSONPointer.

Note: token is not encoded. Use p.PrependString to encode and prepend the token.

func (Pointer) PrependString added in v0.0.3

func (p Pointer) PrependString(token string) Pointer

PrependString encodes and prepends token to the value of p and returns the new JSONPointer.

func (Pointer) String added in v0.0.3

func (p Pointer) String() string

func (Pointer) Tokens added in v0.0.3

func (p Pointer) Tokens() []string

Tokens returns the decoded tokens of the JSONPointer.

func (Pointer) Validate added in v0.0.3

func (p Pointer) Validate() (err error)

Validate performs validation on p. The following checks are performed:

- p must be either empty or start with '/

- p must be properly encoded, meaning that '~' must be immediately followed by a '0' or '1'.

type Resolver

type Resolver interface {
	ResolveJSONPointer(ptr *Pointer, op Operation) (interface{}, error)
}

Resolver is the interface that is implemented by types which can resolve json pointers. The method is expected not to have side effects to the source.

type Token

type Token string

Token is a segment of a JSON Pointer, divided by '/' (%x2F).

func (Token) Bytes

func (t Token) Bytes() []byte

Bytes returns the decoded Bytes of t

func (Token) Index

func (t Token) Index(next int) (int, error)

Index parses t for an index value. If t can be parsed as an int, is equal to or greater than 0 and less than or equal to next then the value is returned. If t is equal to "-" then next is returned. If neither condition is true, -1 and an IndexError is returned.

next must be greater than or equal to 0.

func (Token) Int

func (t Token) Int() (int, error)

Int attempts to parse t as an int. If t can be parsed as an int then the value is returned. If t can not be parsed as an int then an error is returned.

func (Token) Int64

func (t Token) Int64() (int64, error)

Int64 attempts to parse t as an int64. If t can be parsed as an int64 then the value is returned. If t can not be parsed as an int64 then an error is returned.

func (Token) String

func (t Token) String() string

String returns the decoded value of t

func (Token) Uint64

func (t Token) Uint64() (uint64, error)

Uint64 attempts to parse t as an uint64. If t can be parsed as an uint64 then the value is returned. If t can not be parsed as an uint64 then an error is returned.

type Tokens

type Tokens []Token

Tokens is a slice of Tokens.

func (Tokens) Stringers

func (ts Tokens) Stringers() []fmt.Stringer

Stringers returns ts as a slice of fmt.Stringers

func (Tokens) Strings

func (ts Tokens) Strings() []string

Strings returns ts as a slice of strings

type ValueError

type ValueError interface {
	Error
	ValueType() reflect.Type
}

func AsValueError

func AsValueError(err error) (ValueError, bool)

AsValueError returns err as a ValueError, if possible.

Jump to

Keyboard shortcuts

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