Repository navigation
Expand file tree
/
Copy pathpointer.go
More file actions
239 lines (217 loc) · 6.17 KB
/
Copy pathpointer.go
File metadata and controls
239 lines (217 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2016-2026 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.
package jsonptr
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
// 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.
type Pointer []string
// Parse parses a JSON pointer from its text representation.
func Parse(pointer string) (Pointer, error) {
if pointer == "" {
return nil, nil
}
if pointer[0] != '/' {
return nil, ErrSyntax
}
ptr := strings.Split(pointer[1:], "/")
// Optimize for the common case
if strings.IndexByte(pointer, '~') == -1 {
return ptr, nil
}
p := 1
for i, part := range ptr {
var err error
if ptr[i], err = UnescapeString(part); err != nil {
return nil, syntaxError(pointer[:p+len(part)])
}
p += len(part) + 1
}
return ptr, nil
}
// MustParse wraps [Parse] and panics in case of error.
func MustParse(pointer string) Pointer {
ptr, err := Parse(pointer)
if err != nil {
panic(fmt.Errorf("%q: %v", pointer, err))
}
return ptr
}
// UnmarshalText implements [encoding.TextUnmarshaler].
func (ptr *Pointer) UnmarshalText(text []byte) error {
// Parse works on a copy, so the caller's buffer is never aliased nor modified.
p, err := Parse(string(text))
if err != nil {
return err
}
*ptr = p
return nil
}
// String returns a JSON Pointer string, escaping components when necessary:
// '~' is replaced by "~0", '/' by "~1"
func (ptr Pointer) String() string {
if len(ptr) == 0 {
return ""
}
dst := make([]byte, 0, 8*len(ptr))
for _, part := range ptr {
dst = AppendEscape(append(dst, '/'), part)
}
return string(dst)
}
// MarshalText implements [encoding.TextMarshaler].
func (ptr Pointer) MarshalText() (text []byte, err error) {
if len(ptr) == 0 {
return nil, nil
}
dst := make([]byte, 0, 8*len(ptr))
for _, part := range ptr {
dst = AppendEscape(append(dst, '/'), part)
}
return dst, nil
}
// Grow allows to prepare space for growth (before use of [Pointer.Property]/[Pointer.Index]).
func (ptr *Pointer) Grow(n int) {
if cap(*ptr) >= len(*ptr)+n {
return
}
*ptr = append(make(Pointer, 0, len(*ptr)+n), *ptr...)
}
// Copy returns a new, independant, copy of the pointer.
func (ptr Pointer) Copy() Pointer {
return append(Pointer(nil), ptr...)
}
// IsRoot returns true if the pointer is at root (empty).
func (ptr Pointer) IsRoot() bool {
return len(ptr) == 0
}
// Up removes the last element of the pointer.
// The pointer is returned for chaining.
//
// Panics if already at root.
func (ptr *Pointer) Up() *Pointer {
if ptr.IsRoot() {
panic(ErrRoot)
}
*ptr = (*ptr)[:len(*ptr)-1]
return ptr
}
// Pop removes the last element of the pointer and returns it.
//
// Panics if already at root.
func (ptr *Pointer) Pop() string {
last := len(*ptr) - 1
if last < 0 {
panic(ErrRoot)
}
prop := (*ptr)[last]
*ptr = (*ptr)[:last]
return prop
}
// Property moves the pointer deeper, following a property name.
// The pointer is returned for chaining.
func (ptr *Pointer) Property(name string) *Pointer {
*ptr = append(*ptr, name)
return ptr
}
// Index moves the pointer deeper, following an array index.
// The pointer is returned for chaining.
func (ptr *Pointer) Index(index int) *Pointer {
var prop string
if index < 0 {
prop = "-"
} else {
prop = strconv.Itoa(index)
}
return ptr.Property(prop)
}
// LeafName returns the name of the last part of the pointer.
func (ptr Pointer) LeafName() string {
return ptr[len(ptr)-1]
}
// LeafIndex returns the last part of the pointer as an array index.
// -1 is returned for "-".
func (ptr Pointer) LeafIndex() (int, error) {
return arrayIndex(ptr.LeafName())
}
// In returns the value from doc pointed by ptr.
//
// doc may be any document accepted by [Get].
func (ptr Pointer) In(doc interface{}) (interface{}, error) {
for i, key := range ptr {
switch here := (doc).(type) {
case map[string]interface{}:
var ok bool
if doc, ok = here[key]; !ok {
return nil, propertyError(ptr[:i+1].String())
}
case []interface{}:
n, err := arrayIndex(key)
if err != nil || n < 0 || n >= len(here) {
return nil, indexError(ptr[:i+1].String())
}
doc = here[n]
case map[string]json.RawMessage:
var ok bool
if doc, ok = here[key]; !ok {
return nil, propertyError(ptr[:i+1].String())
}
case []json.RawMessage:
n, err := arrayIndex(key)
if err != nil || n < 0 || n >= len(here) {
return nil, indexError(ptr[:i+1].String())
}
doc = here[n]
case JSONDecoder:
v, err := getJSON(here, ptr[i:].String())
if err != nil {
err.rebase(ptr[:i].String())
}
return v, err
case json.RawMessage:
v, err := getRaw(here, ptr[i:].String())
if err != nil {
err.rebase(ptr[:i].String())
}
return v, err
default:
// We report the error at the upper level: the location of the
// value that can't be traversed
return nil, docError(ptr[:i].String(), doc)
}
}
doc, err := getLeaf(doc)
if err != nil {
err.rebase(ptr.String())
}
return doc, err
}
// Set stores value at the location pointed by ptr in the document *pdoc.
// A root pointer replaces the whole document. Otherwise the parent of the
// location must exist: only the leaf is created if it doesn't exist. In an
// array, index "-" appends the value, and an index beyond the end extends
// the array with nulls.
//
// See [Set] for how value is stored, the handling of the documents which
// are not fully deserialized, and the errors reported.
func (ptr Pointer) Set(pdoc *interface{}, value interface{}) error {
// TODO Make an optimised implementation
return Set(pdoc, ptr.String(), value)
}
// Delete removes the value at the location pointed by ptr in the document
// *pdoc, and returns it. An array element is removed by shifting the
// remaining ones. The root of the document can't be deleted ([ErrDeleteRoot]).
//
// See [Delete] for the value returned, the handling of the documents which
// are not fully deserialized, and the errors reported.
func (ptr Pointer) Delete(pdoc *interface{}) (interface{}, error) {
// TODO Make an optimised implementation
return Delete(pdoc, ptr.String())
}