@@ -37,7 +37,7 @@ func SetPathValue(path string, val interface{}, output interface{}) error {
3737 target , field , err := findTargetAtPath (steps , target )
3838 if err != nil {
3939 if err == ErrNotFound {
40- return fmt .Errorf ("at \" %s \" : path not found " , path )
40+ return fmt .Errorf ("at %q: %w " , path , ErrNotFound )
4141 }
4242 collector .Add (err )
4343 return collector .AsSingleError ()
@@ -65,22 +65,22 @@ func GetPathValue(path string, input interface{}) (interface{}, error) {
6565 // Find target place to assign to, field is a non-empty string only if target is a map.
6666 target , field , err := findTargetAtPath (steps , target )
6767 if err != nil {
68- if err == ErrNotFound {
69- return nil , fmt .Errorf ("at \" %s\" : path not found" , path )
70- }
71- return nil , fmt .Errorf ("at \" %s\" : %s" , path , err )
68+ return nil , fmt .Errorf ("at %q: %w" , path , err )
7269 }
70+ // If all steps completed, resolve the target to a real value.
7371 if field == "" {
7472 return target .Interface (), nil
7573 }
76-
77- lookup := target .MapIndex (reflect .ValueOf (field ))
78- if lookup .IsValid () {
74+ // Otherwise, one step is left, look it up using case-insensitive comparison.
75+ if lookup := mapLookupCaseInsensitive (target , field ); lookup != nil {
7976 return lookup .Interface (), nil
8077 }
81- return nil , fmt .Errorf ("at \" %s \" : invalid path " , path )
78+ return nil , fmt .Errorf ("at %q: %w " , path , ErrNotFound )
8279}
8380
81+ // Recursively look up the first step in place, until there are no steps left. If place is ever
82+ // a map with one step left, return that map and final step - this is done in order to enable
83+ // assignment to that map.
8484func findTargetAtPath (steps []string , place reflect.Value ) (reflect.Value , string , error ) {
8585 if len (steps ) == 0 {
8686 return place , "" , nil
@@ -94,6 +94,16 @@ func findTargetAtPath(steps []string, place reflect.Value) (reflect.Value, strin
9494 return place , "" , ErrNotFound
9595 }
9696 return findTargetAtPath (rest , field )
97+ } else if place .Kind () == reflect .Interface {
98+ var inner reflect.Value
99+ if place .IsNil () {
100+ alloc := reflect .New (place .Type ().Elem ())
101+ place .Set (alloc )
102+ inner = alloc .Elem ()
103+ } else {
104+ inner = place .Elem ()
105+ }
106+ return findTargetAtPath (steps , inner )
97107 } else if place .Kind () == reflect .Ptr {
98108 var inner reflect.Value
99109 if place .IsNil () {
@@ -108,9 +118,14 @@ func findTargetAtPath(steps []string, place reflect.Value) (reflect.Value, strin
108118 if place .IsNil () {
109119 place .Set (reflect .MakeMap (place .Type ()))
110120 }
111- // TODO: Handle case where `rest` has more steps and `val` is a struct: more
112- // recursive is needed.
113- return place , s , nil
121+ if len (steps ) == 1 {
122+ return place , s , nil
123+ }
124+ found := mapLookupCaseInsensitive (place , s )
125+ if found == nil {
126+ return place , "" , ErrNotFound
127+ }
128+ return findTargetAtPath (rest , * found )
114129 } else if place .Kind () == reflect .Slice {
115130 num , err := coerceToInt (s )
116131 if err != nil {
@@ -126,6 +141,36 @@ func findTargetAtPath(steps []string, place reflect.Value) (reflect.Value, strin
126141 }
127142}
128143
144+ // Look up value in the map, using case-insensitive string comparison. Return nil if not found
145+ func mapLookupCaseInsensitive (mapValue reflect.Value , k string ) * reflect.Value {
146+ // Try looking up the value without changing the string case
147+ key := reflect .ValueOf (k )
148+ result := mapValue .MapIndex (key )
149+ if result .IsValid () {
150+ return & result
151+ }
152+ // Try lower-casing the key and looking that up
153+ klower := strings .ToLower (k )
154+ key = reflect .ValueOf (klower )
155+ result = mapValue .MapIndex (key )
156+ if result .IsValid () {
157+ return & result
158+ }
159+ // Iterate over the map keys, compare each, using case-insensitive matching
160+ mapKeys := mapValue .MapKeys ()
161+ for _ , mk := range mapKeys {
162+ mstr := mk .String ()
163+ if strings .ToLower (mstr ) == klower {
164+ result = mapValue .MapIndex (mk )
165+ if result .IsValid () {
166+ return & result
167+ }
168+ }
169+ }
170+ // Not found, return nil
171+ return nil
172+ }
173+
129174func coerceToTargetType (val interface {}, place reflect.Value ) (interface {}, error ) {
130175 switch place .Kind () {
131176 case reflect .Bool :
0 commit comments