@@ -20,47 +20,75 @@ const (
2020 pointerSeparator = `/`
2121)
2222
23- // JSONPointable is an interface for structs to implement when they need to customize the
24- // json pointer process
23+ // JSONPointable is an interface for structs to implement,
24+ // when they need to customize the json pointer process or want to avoid the use of reflection.
2525type JSONPointable interface {
26+ // JSONLookup returns a value pointed at this (unescaped) key.
2627 JSONLookup (key string ) (any , error )
2728}
2829
29- // JSONSetable is an interface for structs to implement when they need to customize the
30- // json pointer process
30+ // JSONSetable is an interface for structs to implement,
31+ // when they need to customize the json pointer process or want to avoid the use of reflection.
3132type JSONSetable interface {
33+ // JSONSet sets the value pointed at the (unescaped) key.
3234 JSONSet (key string , value any ) error
3335}
3436
35- // Pointer is a representation of a json pointer
37+ // Pointer is a representation of a json pointer.
38+ //
39+ // Use [Pointer.Get] to retrieve a value or [Pointer.Set] to set a value.
40+ //
41+ // It works with any go type interpreted as a JSON document, which means:
42+ //
43+ // - if a type implements [JSONPointable], its [JSONPointable.JSONLookup] method is used to resolve [Pointer.Get]
44+ // - if a type implements [JSONSetable], its [JSONPointable.JSONSet] method is used to resolve [Pointer.Set]
45+ // - a go map[K]V is interpreted as an object, with type K assignable to a string
46+ // - a go slice []T is interpreted as an array
47+ // - a go struct is interpreted as an object, with exported fields interpreted as keys
48+ // - scalars (e.g. int, float64 ...), channels, functions and go arrays cannot be traversed
49+ //
50+ // For struct s resolved by reflection, key mappings honor the conventional struct tag `json`.
51+ //
52+ // Fields that do not specify a `json` tag, or specify an empty one, or are tagged as `json:"-"` are ignored.
53+ //
54+ // # Limitations
55+ //
56+ // - Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored.
57+ // - anonymous embedded fields are not traversed
3658type Pointer struct {
3759 referenceTokens []string
3860}
3961
40- // New creates a new json pointer for the given string
62+ // New creates a new json pointer from its string representation.
4163func New (jsonPointerString string ) (Pointer , error ) {
4264 var p Pointer
4365 err := p .parse (jsonPointerString )
4466
4567 return p , err
4668}
4769
48- // Get uses the pointer to retrieve a value from a JSON document
70+ // Get uses the pointer to retrieve a value from a JSON document.
71+ //
72+ // It returns the value with its type as a [reflect.Kind] or an error.
4973func (p * Pointer ) Get (document any ) (any , reflect.Kind , error ) {
5074 return p .get (document , jsonname .DefaultJSONNameProvider )
5175}
5276
53- // Set uses the pointer to set a value from a JSON document
77+ // Set uses the pointer to set a value from a data type
78+ // that represent a JSON document.
79+ //
80+ // It returns the updated document.
5481func (p * Pointer ) Set (document any , value any ) (any , error ) {
5582 return document , p .set (document , value , jsonname .DefaultJSONNameProvider )
5683}
5784
58- // DecodedTokens returns the decoded tokens of this JSON pointer
85+ // DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.
5986func (p * Pointer ) DecodedTokens () []string {
6087 result := make ([]string , 0 , len (p .referenceTokens ))
61- for _ , t := range p .referenceTokens {
62- result = append (result , Unescape (t ))
88+ for _ , token := range p .referenceTokens {
89+ result = append (result , Unescape (token ))
6390 }
91+
6492 return result
6593}
6694
@@ -71,9 +99,8 @@ func (p *Pointer) IsEmpty() bool {
7199 return len (p .referenceTokens ) == 0
72100}
73101
74- // String representation of a pointer
102+ // String representation of a pointer.
75103func (p * Pointer ) String () string {
76-
77104 if len (p .referenceTokens ) == 0 {
78105 return emptyPointer
79106 }
@@ -112,13 +139,14 @@ func (p *Pointer) Offset(document string) (int64, error) {
112139 return offset , nil
113140}
114141
115- // "Constructor", parses the given string JSON pointer
142+ // "Constructor", parses the given string JSON pointer.
116143func (p * Pointer ) parse (jsonPointerString string ) error {
117144 if jsonPointerString == emptyPointer {
118145 return nil
119146 }
120147
121148 if ! strings .HasPrefix (jsonPointerString , pointerSeparator ) {
149+ // non empty pointer must start with "/"
122150 return errors .Join (ErrInvalidStart , ErrPointer )
123151 }
124152
@@ -135,7 +163,7 @@ func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, refle
135163
136164 kind := reflect .Invalid
137165
138- // Full document when empty
166+ // full document when empty
139167 if len (p .referenceTokens ) == 0 {
140168 return node , kind , nil
141169 }
@@ -161,6 +189,7 @@ func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error
161189
162190 if knd != reflect .Pointer && knd != reflect .Struct && knd != reflect .Map && knd != reflect .Slice && knd != reflect .Array {
163191 return errors .Join (
192+ fmt .Errorf ("unexpected type: %T" , node ), //nolint:err113 // err wrapping is carried out by errors.Join, not fmt.Errorf.
164193 ErrUnsupportedValueType ,
165194 ErrPointer ,
166195 )
@@ -222,7 +251,7 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
222251 rValue := reflect .Indirect (reflect .ValueOf (node ))
223252 kind := rValue .Kind ()
224253
225- switch kind { //nolint:exhaustive
254+ switch kind {
226255 case reflect .Struct :
227256 nm , ok := nameProvider .GetGoNameForType (rValue .Type (), decodedToken )
228257 if ! ok {
@@ -236,7 +265,7 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
236265 mv := rValue .MapIndex (kv )
237266
238267 if ! mv .IsValid () {
239- return nil , fmt . Errorf ( "object has no key %q: %w" , decodedToken , ErrPointer )
268+ return nil , errNoKey ( decodedToken )
240269 }
241270
242271 return typeFromValue (mv ), nil
@@ -249,13 +278,13 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
249278
250279 sLength := rValue .Len ()
251280 if tokenIndex < 0 || tokenIndex >= sLength {
252- return nil , fmt . Errorf ( "index out of bounds array[0,%d] index '%d': %w" , sLength , tokenIndex , ErrPointer )
281+ return nil , errOutOfBounds ( sLength , tokenIndex )
253282 }
254283
255284 return typeFromValue (rValue .Index (tokenIndex )), nil
256285
257286 default :
258- return nil , fmt . Errorf ( "invalid token reference %q: %w" , decodedToken , ErrPointer )
287+ return nil , errInvalidReference ( decodedToken )
259288 }
260289}
261290
@@ -265,7 +294,7 @@ func isNil(input any) bool {
265294 }
266295
267296 kind := reflect .TypeOf (input ).Kind ()
268- switch kind { //nolint:exhaustive
297+ switch kind {
269298 case reflect .Pointer , reflect .Map , reflect .Slice , reflect .Chan :
270299 return reflect .ValueOf (input ).IsNil ()
271300 default :
@@ -281,12 +310,12 @@ func typeFromValue(v reflect.Value) any {
281310 return v .Interface ()
282311}
283312
284- // GetForToken gets a value for a json pointer token 1 level deep
313+ // GetForToken gets a value for a json pointer token 1 level deep.
285314func GetForToken (document any , decodedToken string ) (any , reflect.Kind , error ) {
286315 return getSingleImpl (document , decodedToken , jsonname .DefaultJSONNameProvider )
287316}
288317
289- // SetForToken gets a value for a json pointer token 1 level deep
318+ // SetForToken sets a value for a json pointer token 1 level deep.
290319func SetForToken (document any , decodedToken string , value any ) (any , error ) {
291320 return document , setSingleImpl (document , value , decodedToken , jsonname .DefaultJSONNameProvider )
292321}
@@ -309,13 +338,15 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
309338 return getSingleImpl (* typed , decodedToken , nameProvider )
310339 }
311340
312- switch kind { //nolint:exhaustive
341+ switch kind {
313342 case reflect .Struct :
314343 nm , ok := nameProvider .GetGoNameForType (rValue .Type (), decodedToken )
315344 if ! ok {
316345 return nil , kind , fmt .Errorf ("object has no field %q: %w" , decodedToken , ErrPointer )
317346 }
347+
318348 fld := rValue .FieldByName (nm )
349+
319350 return fld .Interface (), kind , nil
320351
321352 case reflect .Map :
@@ -325,7 +356,8 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
325356 if mv .IsValid () {
326357 return mv .Interface (), kind , nil
327358 }
328- return nil , kind , fmt .Errorf ("object has no key %q: %w" , decodedToken , ErrPointer )
359+
360+ return nil , kind , errNoKey (decodedToken )
329361
330362 case reflect .Slice :
331363 tokenIndex , err := strconv .Atoi (decodedToken )
@@ -334,14 +366,14 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
334366 }
335367 sLength := rValue .Len ()
336368 if tokenIndex < 0 || tokenIndex >= sLength {
337- return nil , kind , fmt . Errorf ( "index out of bounds array[0,%d] index '%d': %w" , sLength - 1 , tokenIndex , ErrPointer )
369+ return nil , kind , errOutOfBounds ( sLength , tokenIndex )
338370 }
339371
340372 elem := rValue .Index (tokenIndex )
341373 return elem .Interface (), kind , nil
342374
343375 default :
344- return nil , kind , fmt . Errorf ( "invalid token reference %q: %w" , decodedToken , ErrPointer )
376+ return nil , kind , errInvalidReference ( decodedToken )
345377 }
346378}
347379
@@ -357,7 +389,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
357389 return ns .JSONSet (decodedToken , data )
358390 }
359391
360- switch rValue .Kind () { //nolint:exhaustive
392+ switch rValue .Kind () {
361393 case reflect .Struct :
362394 nm , ok := nameProvider .GetGoNameForType (rValue .Type (), decodedToken )
363395 if ! ok {
@@ -381,7 +413,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
381413 }
382414 sLength := rValue .Len ()
383415 if tokenIndex < 0 || tokenIndex >= sLength {
384- return fmt . Errorf ( "index out of bounds array[0,%d] index '%d': %w" , sLength , tokenIndex , ErrPointer )
416+ return errOutOfBounds ( sLength , tokenIndex )
385417 }
386418
387419 elem := rValue .Index (tokenIndex )
@@ -392,7 +424,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
392424 return nil
393425
394426 default :
395- return fmt . Errorf ( "invalid token reference %q: %w" , decodedToken , ErrPointer )
427+ return errInvalidReference ( decodedToken )
396428 }
397429}
398430
@@ -430,7 +462,7 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
430462func offsetSingleArray (dec * json.Decoder , decodedToken string ) (int64 , error ) {
431463 idx , err := strconv .Atoi (decodedToken )
432464 if err != nil {
433- return 0 , fmt .Errorf ("token reference %q is not a number: %v : %w" , decodedToken , err , ErrPointer )
465+ return 0 , fmt .Errorf ("token reference %q is not a number: %w : %w" , decodedToken , err , ErrPointer )
434466 }
435467 var i int
436468 for i = 0 ; i < idx && dec .More (); i ++ {
@@ -461,6 +493,7 @@ func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
461493}
462494
463495// drainSingle drains a single level of object or array.
496+ //
464497// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed.
465498func drainSingle (dec * json.Decoder ) error {
466499 for dec .More () {
@@ -490,7 +523,7 @@ func drainSingle(dec *json.Decoder) error {
490523 return nil
491524}
492525
493- // Specific JSON pointer encoding here
526+ // JSON pointer encoding:
494527// ~0 => ~
495528// ~1 => /
496529// ... and vice versa
@@ -507,12 +540,19 @@ var (
507540 decRefTokReplacer = strings .NewReplacer (decRefTok1 , encRefTok1 , decRefTok0 , encRefTok0 ) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
508541)
509542
510- // Unescape unescapes a json pointer reference token string to the original representation
543+ // Unescape unescapes a json pointer reference token string to the original representation.
511544func Unescape (token string ) string {
512545 return encRefTokReplacer .Replace (token )
513546}
514547
515- // Escape escapes a pointer reference token string
548+ // Escape escapes a pointer reference token string.
549+ //
550+ // The JSONPointer specification defines "/" as a separator and "~" as an escape prefix.
551+ //
552+ // Keys containing such characters are escaped with the following rules:
553+ //
554+ // - "~" is escaped as "~0"
555+ // - "/" is escaped as "~1"
516556func Escape (token string ) string {
517557 return decRefTokReplacer .Replace (token )
518558}
0 commit comments