jsonptr

package module
v0.0.0-...-38530b8 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2024 License: Apache-2.0 Imports: 6 Imported by: 15

README

jsonptr - JSON Pointer (RFC 6901) for Go

GoDoc codecov Go Report Card

Features

Goals:

  1. First-class interface
  2. Correctness (most existing open source Go implementations have limitations in their interface or have implementation bugs)
    • Full testsuite (work in progress)
    • Reject invalid escapes (regexp /~[^01]/)
    • Allow any JSON value as leaf node
    • Allow any JSON value as root (not just a map[string]interface{})
    • Allow to get/set the root of the document with the empty pointer ""
  3. Speed (see benchmark)
    • No reflect
    • Optimised parsing

Example

package main

import (
    "fmt"
    "github.com/dolmen-go/jsonptr"
)

func main() {
    // JSON: { "a": [ 1 ] }
    document := map[string]interface{}{
        "a": []interface{}{
            1,
        },
    }

    val, err := jsonptr.Get(document, `/a/0`)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
    } else {
        fmt.Printf("Got: %v\n", val)
    }
}

Status

Production ready.

The aim is code coverage of 100%. Use go coverage tools and consider any code not covered by the testsuite as never tested and full of bugs.

Todo:

  • tests of error cases

License

Copyright 2016-2020 Olivier Mengué

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Package jsonptr implements JSON Pointer (RFC 6901) lookup. Fast, with strong testsuite.

Any part of a data tree made of []interface{} or map[string]interface{} may be dereferenced with a JSON Pointer.

Specification: https://tools.ietf.org/html/rfc6901

Example

Example from https://tools.ietf.org/html/rfc6901#section-5

package main

import (
	"encoding/json"
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	const JSON = `
{
   "foo": ["bar", "baz"],
   "": 0,
   "a/b": 1,
   "c%d": 2,
   "e^f": 3,
   "g|h": 4,
   "i\\j": 5,
   "k\"l": 6,
   " ": 7,
   "m~n": 8
}`
	var doc interface{}
	_ = json.Unmarshal([]byte(JSON), &doc)

	for _, ptr := range []string{
		"/foo",
		"/foo/0",
		"/",
		"/a~1b",
		"/c%d",
		"/e^f",
		"/g|h",
		"/i\\j",
		"/k\"l",
		"/ ",
		"/m~0n",
	} {
		result, _ := jsonptr.Get(doc, ptr)
		fmt.Printf("%-12q %#v\n", ptr, result)
	}
}
Output:

"/foo"       []interface {}{"bar", "baz"}
"/foo/0"     "bar"
"/"          0
"/a~1b"      1
"/c%d"       2
"/e^f"       3
"/g|h"       4
"/i\\j"      5
"/k\"l"      6
"/ "         7
"/m~0n"      8

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrSyntax   = errors.New("invalid JSON pointer")
	ErrIndex    = errors.New("invalid array index")
	ErrProperty = errors.New("property not found")

	ErrRoot = errors.New("can't go up from root")

	ErrDeleteRoot = errors.New("can't delete root")

	ErrUsage = errors.New("invalid use of jsonptr.UnescapeString on string with '/'")
)

Functions

func AppendEscape

func AppendEscape(dst []byte, name string) []byte

AppendEscape appends the escaped name to dst and returns it. The buffer grows (and so is reallocated) if necessary.

func Delete

func Delete(pdoc *interface{}, ptr string) (interface{}, error)

Delete removes an object property or an array element (and shifts remaining ones). It can't be applied on root.

func EscapeString

func EscapeString(name string) string

EscapeString escapes a property name with JSON Pointer escapes:

'~' => `~0`
'/' => `~1`
Example
package main

import (
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	fmt.Println(jsonptr.EscapeString("a~b c/d"))
}
Output:

a~0b c~1d

func Get

func Get(doc interface{}, ptr string) (interface{}, error)

Get extracts a value from a JSON-like data tree.

doc may be:

In case of error a PtrError is returned.

func MustValue

func MustValue(v interface{}, err error) interface{}

MustValue allows to wrap a call to some jsonptr function which returns a value to transform any error into a panic.

func Set

func Set(doc *interface{}, ptr string, value interface{}) error

Set modifies a JSON-like data tree.

In case of error a PtrError is returned.

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {

	newArray := []interface{}(nil)
	newObject := map[string]interface{}(nil)

	var doc interface{}

	for _, step := range []struct {
		where string
		what  interface{}
	}{
		{"", newObject},
		{"/arr", newArray},
		{"/arr/-", 3},
		{"/arr/-", 2},
		{"/arr/-", 1},
		{"/obj", newObject},
		{"/obj/str", "hello"},
		{"/obj/bool", true},
		{"/arr/-", 0},
		{"/obj/", nil},
	} {
		err := jsonptr.Set(&doc, step.where, step.what)
		if err != nil {
			panic(err)
		}
		//fmt.Printf("%#v\n", doc)
	}

	fmt.Println(func() string { x, _ := json.Marshal(doc); return string(x) }())
}
Output:

{"arr":[3,2,1,0],"obj":{"":null,"bool":true,"str":"hello"}}

func Unescape

func Unescape(b []byte) ([]byte, error)

Unescape unescapes a property name in place:

`~1` => '/'
`~0` => '~'

Any '~' followed by something else (or nothing) is an error ErrSyntax. Any '/' is an error ErrSyntax.

func UnescapeString

func UnescapeString(token string) (string, error)

UnescapeString unescapes a property name:

`~1` => '/'
`~0` => '~'

Any '~' followed by something else (or nothing) is an error ErrSyntax. If the input contains '/' the result is undefined (may panic).

Example
package main

import (
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	s, _ := jsonptr.UnescapeString("a~0b c~1d")
	fmt.Println(s)
}
Output:

a~b c/d
Example (Error)
package main

import (
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	_, err := jsonptr.UnescapeString("a~x")
	fmt.Println("jsonptr.ErrSyntax?", err == jsonptr.ErrSyntax)
	fmt.Println(err)
}
Output:

jsonptr.ErrSyntax? true
invalid JSON pointer

Types

type BadPointerError

type BadPointerError struct {
	// Ptr is the substring of the original pointer where the error occurred
	BadPtr string
	// Err is ErrSyntax
	Err error
}

BadPointerError signals JSON Pointer parsing errors

func (*BadPointerError) Error

func (e *BadPointerError) Error() string

Error implements the 'error' interface

func (*BadPointerError) Unwrap

func (e *BadPointerError) Unwrap() error

Unwrap allows to unwrap the error (see errors.Unwrap).

type DocumentError

type DocumentError struct {
	Ptr string
	Err error
}

DocumentError signals a document that can't be processed by this library

func (*DocumentError) Error

func (e *DocumentError) Error() string

Error implements the 'error' interface.

func (*DocumentError) Unwrap

func (e *DocumentError) Unwrap() error

Unwrap allows to unwrap the error (see errors.Unwrap).

type JSONDecoder

type JSONDecoder interface {
	Token() (json.Token, error)
	More() bool
	Decode(interface{}) error
}

JSONDecoder is a subset of the interface of encoding/json.Decoder. It can be used as an input to Get().

Example

ExampleJSONDecoder shows how to extract from a streamed JSON document.

package main

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing/iotest"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	stream := iotest.OneByteReader(strings.NewReader(`{"a": 42,"b":5}`))
	decoder := json.NewDecoder(stream)

	result, _ := jsonptr.Get(decoder, "/a")
	fmt.Println(result)
}
Output:

42

type Pointer

type Pointer []string

Pointer represents a mutable parsed JSON Pointer.

The Go representation is a array of non-encoded path elements. This allows to use type conversion from/to a []string.

Example (Conversion)
package main

import (
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	fmt.Printf("%q\n\n", jsonptr.Pointer{"foo", "bar", "a/b", "x~y"})

	for _, ptr := range []jsonptr.Pointer{
		nil,
		{},
		{"a", "b"},
		{"a~/b"},
	} {
		fmt.Printf("%q\n", ptr.String())
	}
}
Output:

"/foo/bar/a~1b/x~0y"

""
""
"/a/b"
"/a~0~1b"
Example (Navigation)
package main

import (
	"fmt"

	"github.com/dolmen-go/jsonptr"
)

func main() {
	ptr := jsonptr.Pointer{}
	fmt.Printf("%q\n", ptr)

	ptr.Property("foo").Index(3).Property("a/b")
	fmt.Printf("%q\n", ptr.String())

	ptr.Up()
	fmt.Printf("%q\n", ptr)

	ptr.Property("c~d")
	fmt.Printf("%q\n", ptr)

}
Output:

""
"/foo/3/a~1b"
"/foo/3"
"/foo/3/c~0d"

func MustParse

func MustParse(pointer string) Pointer

MustParse wraps Parse and panics in case of error.

func Parse

func Parse(pointer string) (Pointer, error)

Parse parses a JSON pointer from its text representation.

func (Pointer) Copy

func (ptr Pointer) Copy() Pointer

Copy returns a new, independant, copy of the pointer.

func (Pointer) Delete

func (ptr Pointer) Delete(pdoc *interface{}) (interface{}, error)

Delete removes an object property or an array element (and shifts remaining ones). It can't be applied on root.

func (*Pointer) Grow

func (ptr *Pointer) Grow(n int)

Grow allows to prepare space for growth (before use of Pointer.Property/Pointer.Index).

func (Pointer) In

func (ptr Pointer) In(doc interface{}) (interface{}, error)

In returns the value from doc pointed by ptr.

doc may be a deserialized document, or a encoding/json.RawMessage.

func (*Pointer) Index

func (ptr *Pointer) Index(index int) *Pointer

Index moves the pointer deeper, following an array index. The pointer is returned for chaining.

func (Pointer) IsRoot

func (ptr Pointer) IsRoot() bool

IsRoot returns true if the pointer is at root (empty).

func (Pointer) LeafIndex

func (ptr Pointer) LeafIndex() (int, error)

LeafIndex returns the last part of the pointer as an array index. -1 is returned for "-".

func (Pointer) LeafName

func (ptr Pointer) LeafName() string

LeafName returns the name of the last part of the pointer.

func (Pointer) MarshalText

func (ptr Pointer) MarshalText() (text []byte, err error)

MarshalText implements encoding.TextMarshaler.

func (*Pointer) Pop

func (ptr *Pointer) Pop() string

Pop removes the last element of the pointer and returns it.

Panics if already at root.

func (*Pointer) Property

func (ptr *Pointer) Property(name string) *Pointer

Property moves the pointer deeper, following a property name. The pointer is returned for chaining.

func (Pointer) Set

func (ptr Pointer) Set(pdoc *interface{}, value interface{}) error

Set changes a value in document pdoc at location pointed by ptr.

func (Pointer) String

func (ptr Pointer) String() string

String returns a JSON Pointer string, escaping components when necessary: '~' is replaced by "~0", '/' by "~1"

func (*Pointer) UnmarshalText

func (ptr *Pointer) UnmarshalText(text []byte) error

MarshalText implements encoding.TextUnmarshaler.

func (*Pointer) Up

func (ptr *Pointer) Up() *Pointer

Up removes the last element of the pointer. The pointer is returned for chaining.

Panics if already at root.

type PtrError

type PtrError struct {
	// Ptr is the substring of the original pointer where the error occurred.
	Ptr string
	// Err is one of ErrIndex, ErrProperty.
	Err error
}

PtrError signals JSON Pointer navigation errors.

func (*PtrError) Error

func (e *PtrError) Error() string

Error implements the 'error' interface

func (*PtrError) Unwrap

func (e *PtrError) Unwrap() error

Unwrap allows to unwrap the error (see errors.Unwrap).

Jump to

Keyboard shortcuts

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