decode_hooks.go raw
1 package mapstructure
2
3 import (
4 "encoding"
5 "errors"
6 "fmt"
7 "net"
8 "net/netip"
9 "net/url"
10 "reflect"
11 "strconv"
12 "strings"
13 "time"
14 )
15
16 // typedDecodeHook takes a raw DecodeHookFunc (an any) and turns
17 // it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
18 func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
19 // Create variables here so we can reference them with the reflect pkg
20 var f1 DecodeHookFuncType
21 var f2 DecodeHookFuncKind
22 var f3 DecodeHookFuncValue
23
24 // Fill in the variables into this interface and the rest is done
25 // automatically using the reflect package.
26 potential := []any{f1, f2, f3}
27
28 v := reflect.ValueOf(h)
29 vt := v.Type()
30 for _, raw := range potential {
31 pt := reflect.ValueOf(raw).Type()
32 if vt.ConvertibleTo(pt) {
33 return v.Convert(pt).Interface()
34 }
35 }
36
37 return nil
38 }
39
40 // cachedDecodeHook takes a raw DecodeHookFunc (an any) and turns
41 // it into a closure to be used directly
42 // if the type fails to convert we return a closure always erroring to keep the previous behaviour
43 func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (any, error) {
44 switch f := typedDecodeHook(raw).(type) {
45 case DecodeHookFuncType:
46 return func(from reflect.Value, to reflect.Value) (any, error) {
47 return f(from.Type(), to.Type(), from.Interface())
48 }
49 case DecodeHookFuncKind:
50 return func(from reflect.Value, to reflect.Value) (any, error) {
51 return f(from.Kind(), to.Kind(), from.Interface())
52 }
53 case DecodeHookFuncValue:
54 return func(from reflect.Value, to reflect.Value) (any, error) {
55 return f(from, to)
56 }
57 default:
58 return func(from reflect.Value, to reflect.Value) (any, error) {
59 return nil, errors.New("invalid decode hook signature")
60 }
61 }
62 }
63
64 // DecodeHookExec executes the given decode hook. This should be used
65 // since it'll naturally degrade to the older backwards compatible DecodeHookFunc
66 // that took reflect.Kind instead of reflect.Type.
67 func DecodeHookExec(
68 raw DecodeHookFunc,
69 from reflect.Value, to reflect.Value,
70 ) (any, error) {
71 switch f := typedDecodeHook(raw).(type) {
72 case DecodeHookFuncType:
73 return f(from.Type(), to.Type(), from.Interface())
74 case DecodeHookFuncKind:
75 return f(from.Kind(), to.Kind(), from.Interface())
76 case DecodeHookFuncValue:
77 return f(from, to)
78 default:
79 return nil, errors.New("invalid decode hook signature")
80 }
81 }
82
83 // ComposeDecodeHookFunc creates a single DecodeHookFunc that
84 // automatically composes multiple DecodeHookFuncs.
85 //
86 // The composed funcs are called in order, with the result of the
87 // previous transformation.
88 func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
89 cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(fs))
90 for _, f := range fs {
91 cached = append(cached, cachedDecodeHook(f))
92 }
93 return func(f reflect.Value, t reflect.Value) (any, error) {
94 var err error
95 data := f.Interface()
96
97 newFrom := f
98 for _, c := range cached {
99 data, err = c(newFrom, t)
100 if err != nil {
101 return nil, err
102 }
103 if v, ok := data.(reflect.Value); ok {
104 newFrom = v
105 } else {
106 newFrom = reflect.ValueOf(data)
107 }
108 }
109
110 return data, nil
111 }
112 }
113
114 // OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
115 // If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
116 func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
117 cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(ff))
118 for _, f := range ff {
119 cached = append(cached, cachedDecodeHook(f))
120 }
121 return func(a, b reflect.Value) (any, error) {
122 var allErrs string
123 var out any
124 var err error
125
126 for _, c := range cached {
127 out, err = c(a, b)
128 if err != nil {
129 allErrs += err.Error() + "\n"
130 continue
131 }
132
133 return out, nil
134 }
135
136 return nil, errors.New(allErrs)
137 }
138 }
139
140 // StringToSliceHookFunc returns a DecodeHookFunc that converts
141 // string to []string by splitting on the given sep.
142 func StringToSliceHookFunc(sep string) DecodeHookFunc {
143 return func(
144 f reflect.Type,
145 t reflect.Type,
146 data any,
147 ) (any, error) {
148 if f.Kind() != reflect.String {
149 return data, nil
150 }
151 if t != reflect.SliceOf(f) {
152 return data, nil
153 }
154
155 raw := data.(string)
156 if raw == "" {
157 return []string{}, nil
158 }
159
160 return strings.Split(raw, sep), nil
161 }
162 }
163
164 // StringToWeakSliceHookFunc brings back the old (pre-v2) behavior of [StringToSliceHookFunc].
165 //
166 // As of mapstructure v2.0.0 [StringToSliceHookFunc] checks if the return type is a string slice.
167 // This function removes that check.
168 func StringToWeakSliceHookFunc(sep string) DecodeHookFunc {
169 return func(
170 f reflect.Type,
171 t reflect.Type,
172 data any,
173 ) (any, error) {
174 if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
175 return data, nil
176 }
177
178 raw := data.(string)
179 if raw == "" {
180 return []string{}, nil
181 }
182
183 return strings.Split(raw, sep), nil
184 }
185 }
186
187 // StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
188 // strings to time.Duration.
189 func StringToTimeDurationHookFunc() DecodeHookFunc {
190 return func(
191 f reflect.Type,
192 t reflect.Type,
193 data any,
194 ) (any, error) {
195 if f.Kind() != reflect.String {
196 return data, nil
197 }
198 if t != reflect.TypeOf(time.Duration(5)) {
199 return data, nil
200 }
201
202 // Convert it by parsing
203 d, err := time.ParseDuration(data.(string))
204
205 return d, wrapTimeParseDurationError(err)
206 }
207 }
208
209 // StringToTimeLocationHookFunc returns a DecodeHookFunc that converts
210 // strings to *time.Location.
211 func StringToTimeLocationHookFunc() DecodeHookFunc {
212 return func(
213 f reflect.Type,
214 t reflect.Type,
215 data any,
216 ) (any, error) {
217 if f.Kind() != reflect.String {
218 return data, nil
219 }
220 if t != reflect.TypeOf(time.Local) {
221 return data, nil
222 }
223 d, err := time.LoadLocation(data.(string))
224
225 return d, wrapTimeParseLocationError(err)
226 }
227 }
228
229 // StringToURLHookFunc returns a DecodeHookFunc that converts
230 // strings to *url.URL.
231 func StringToURLHookFunc() DecodeHookFunc {
232 return func(
233 f reflect.Type,
234 t reflect.Type,
235 data any,
236 ) (any, error) {
237 if f.Kind() != reflect.String {
238 return data, nil
239 }
240 if t != reflect.TypeOf(&url.URL{}) {
241 return data, nil
242 }
243
244 // Convert it by parsing
245 u, err := url.Parse(data.(string))
246
247 return u, wrapUrlError(err)
248 }
249 }
250
251 // StringToIPHookFunc returns a DecodeHookFunc that converts
252 // strings to net.IP
253 func StringToIPHookFunc() DecodeHookFunc {
254 return func(
255 f reflect.Type,
256 t reflect.Type,
257 data any,
258 ) (any, error) {
259 if f.Kind() != reflect.String {
260 return data, nil
261 }
262 if t != reflect.TypeOf(net.IP{}) {
263 return data, nil
264 }
265
266 // Convert it by parsing
267 ip := net.ParseIP(data.(string))
268 if ip == nil {
269 return net.IP{}, fmt.Errorf("failed parsing ip")
270 }
271
272 return ip, nil
273 }
274 }
275
276 // StringToIPNetHookFunc returns a DecodeHookFunc that converts
277 // strings to net.IPNet
278 func StringToIPNetHookFunc() DecodeHookFunc {
279 return func(
280 f reflect.Type,
281 t reflect.Type,
282 data any,
283 ) (any, error) {
284 if f.Kind() != reflect.String {
285 return data, nil
286 }
287 if t != reflect.TypeOf(net.IPNet{}) {
288 return data, nil
289 }
290
291 // Convert it by parsing
292 _, net, err := net.ParseCIDR(data.(string))
293 return net, wrapNetParseError(err)
294 }
295 }
296
297 // StringToTimeHookFunc returns a DecodeHookFunc that converts
298 // strings to time.Time.
299 func StringToTimeHookFunc(layout string) DecodeHookFunc {
300 return func(
301 f reflect.Type,
302 t reflect.Type,
303 data any,
304 ) (any, error) {
305 if f.Kind() != reflect.String {
306 return data, nil
307 }
308 if t != reflect.TypeOf(time.Time{}) {
309 return data, nil
310 }
311
312 // Convert it by parsing
313 ti, err := time.Parse(layout, data.(string))
314
315 return ti, wrapTimeParseError(err)
316 }
317 }
318
319 // WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
320 // the decoder.
321 //
322 // Note that this is significantly different from the WeaklyTypedInput option
323 // of the DecoderConfig.
324 func WeaklyTypedHook(
325 f reflect.Kind,
326 t reflect.Kind,
327 data any,
328 ) (any, error) {
329 dataVal := reflect.ValueOf(data)
330 switch t {
331 case reflect.String:
332 switch f {
333 case reflect.Bool:
334 if dataVal.Bool() {
335 return "1", nil
336 }
337 return "0", nil
338 case reflect.Float32:
339 return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
340 case reflect.Int:
341 return strconv.FormatInt(dataVal.Int(), 10), nil
342 case reflect.Slice:
343 dataType := dataVal.Type()
344 elemKind := dataType.Elem().Kind()
345 if elemKind == reflect.Uint8 {
346 return string(dataVal.Interface().([]uint8)), nil
347 }
348 case reflect.Uint:
349 return strconv.FormatUint(dataVal.Uint(), 10), nil
350 }
351 }
352
353 return data, nil
354 }
355
356 func RecursiveStructToMapHookFunc() DecodeHookFunc {
357 return func(f reflect.Value, t reflect.Value) (any, error) {
358 if f.Kind() != reflect.Struct {
359 return f.Interface(), nil
360 }
361
362 var i any = struct{}{}
363 if t.Type() != reflect.TypeOf(&i).Elem() {
364 return f.Interface(), nil
365 }
366
367 m := make(map[string]any)
368 t.Set(reflect.ValueOf(m))
369
370 return f.Interface(), nil
371 }
372 }
373
374 // TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
375 // strings to the UnmarshalText function, when the target type
376 // implements the encoding.TextUnmarshaler interface
377 func TextUnmarshallerHookFunc() DecodeHookFuncType {
378 return func(
379 f reflect.Type,
380 t reflect.Type,
381 data any,
382 ) (any, error) {
383 if f.Kind() != reflect.String {
384 return data, nil
385 }
386 result := reflect.New(t).Interface()
387 unmarshaller, ok := result.(encoding.TextUnmarshaler)
388 if !ok {
389 return data, nil
390 }
391 str, ok := data.(string)
392 if !ok {
393 str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String()
394 }
395 if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
396 return nil, err
397 }
398 return result, nil
399 }
400 }
401
402 // StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts
403 // strings to netip.Addr.
404 func StringToNetIPAddrHookFunc() DecodeHookFunc {
405 return func(
406 f reflect.Type,
407 t reflect.Type,
408 data any,
409 ) (any, error) {
410 if f.Kind() != reflect.String {
411 return data, nil
412 }
413 if t != reflect.TypeOf(netip.Addr{}) {
414 return data, nil
415 }
416
417 // Convert it by parsing
418 addr, err := netip.ParseAddr(data.(string))
419
420 return addr, wrapNetIPParseAddrError(err)
421 }
422 }
423
424 // StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts
425 // strings to netip.AddrPort.
426 func StringToNetIPAddrPortHookFunc() DecodeHookFunc {
427 return func(
428 f reflect.Type,
429 t reflect.Type,
430 data any,
431 ) (any, error) {
432 if f.Kind() != reflect.String {
433 return data, nil
434 }
435 if t != reflect.TypeOf(netip.AddrPort{}) {
436 return data, nil
437 }
438
439 // Convert it by parsing
440 addrPort, err := netip.ParseAddrPort(data.(string))
441
442 return addrPort, wrapNetIPParseAddrPortError(err)
443 }
444 }
445
446 // StringToNetIPPrefixHookFunc returns a DecodeHookFunc that converts
447 // strings to netip.Prefix.
448 func StringToNetIPPrefixHookFunc() DecodeHookFunc {
449 return func(
450 f reflect.Type,
451 t reflect.Type,
452 data any,
453 ) (any, error) {
454 if f.Kind() != reflect.String {
455 return data, nil
456 }
457 if t != reflect.TypeOf(netip.Prefix{}) {
458 return data, nil
459 }
460
461 // Convert it by parsing
462 prefix, err := netip.ParsePrefix(data.(string))
463
464 return prefix, wrapNetIPParsePrefixError(err)
465 }
466 }
467
468 // StringToBasicTypeHookFunc returns a DecodeHookFunc that converts
469 // strings to basic types.
470 // int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128
471 func StringToBasicTypeHookFunc() DecodeHookFunc {
472 return ComposeDecodeHookFunc(
473 StringToInt8HookFunc(),
474 StringToUint8HookFunc(),
475 StringToInt16HookFunc(),
476 StringToUint16HookFunc(),
477 StringToInt32HookFunc(),
478 StringToUint32HookFunc(),
479 StringToInt64HookFunc(),
480 StringToUint64HookFunc(),
481 StringToIntHookFunc(),
482 StringToUintHookFunc(),
483 StringToFloat32HookFunc(),
484 StringToFloat64HookFunc(),
485 StringToBoolHookFunc(),
486 // byte and rune are aliases for uint8 and int32 respectively
487 // StringToByteHookFunc(),
488 // StringToRuneHookFunc(),
489 StringToComplex64HookFunc(),
490 StringToComplex128HookFunc(),
491 )
492 }
493
494 // StringToInt8HookFunc returns a DecodeHookFunc that converts
495 // strings to int8.
496 func StringToInt8HookFunc() DecodeHookFunc {
497 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
498 if f.Kind() != reflect.String || t.Kind() != reflect.Int8 {
499 return data, nil
500 }
501
502 // Convert it by parsing
503 i64, err := strconv.ParseInt(data.(string), 0, 8)
504 return int8(i64), wrapStrconvNumError(err)
505 }
506 }
507
508 // StringToUint8HookFunc returns a DecodeHookFunc that converts
509 // strings to uint8.
510 func StringToUint8HookFunc() DecodeHookFunc {
511 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
512 if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 {
513 return data, nil
514 }
515
516 // Convert it by parsing
517 u64, err := strconv.ParseUint(data.(string), 0, 8)
518 return uint8(u64), wrapStrconvNumError(err)
519 }
520 }
521
522 // StringToInt16HookFunc returns a DecodeHookFunc that converts
523 // strings to int16.
524 func StringToInt16HookFunc() DecodeHookFunc {
525 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
526 if f.Kind() != reflect.String || t.Kind() != reflect.Int16 {
527 return data, nil
528 }
529
530 // Convert it by parsing
531 i64, err := strconv.ParseInt(data.(string), 0, 16)
532 return int16(i64), wrapStrconvNumError(err)
533 }
534 }
535
536 // StringToUint16HookFunc returns a DecodeHookFunc that converts
537 // strings to uint16.
538 func StringToUint16HookFunc() DecodeHookFunc {
539 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
540 if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 {
541 return data, nil
542 }
543
544 // Convert it by parsing
545 u64, err := strconv.ParseUint(data.(string), 0, 16)
546 return uint16(u64), wrapStrconvNumError(err)
547 }
548 }
549
550 // StringToInt32HookFunc returns a DecodeHookFunc that converts
551 // strings to int32.
552 func StringToInt32HookFunc() DecodeHookFunc {
553 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
554 if f.Kind() != reflect.String || t.Kind() != reflect.Int32 {
555 return data, nil
556 }
557
558 // Convert it by parsing
559 i64, err := strconv.ParseInt(data.(string), 0, 32)
560 return int32(i64), wrapStrconvNumError(err)
561 }
562 }
563
564 // StringToUint32HookFunc returns a DecodeHookFunc that converts
565 // strings to uint32.
566 func StringToUint32HookFunc() DecodeHookFunc {
567 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
568 if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 {
569 return data, nil
570 }
571
572 // Convert it by parsing
573 u64, err := strconv.ParseUint(data.(string), 0, 32)
574 return uint32(u64), wrapStrconvNumError(err)
575 }
576 }
577
578 // StringToInt64HookFunc returns a DecodeHookFunc that converts
579 // strings to int64.
580 func StringToInt64HookFunc() DecodeHookFunc {
581 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
582 if f.Kind() != reflect.String || t.Kind() != reflect.Int64 {
583 return data, nil
584 }
585
586 // Convert it by parsing
587 i64, err := strconv.ParseInt(data.(string), 0, 64)
588 return int64(i64), wrapStrconvNumError(err)
589 }
590 }
591
592 // StringToUint64HookFunc returns a DecodeHookFunc that converts
593 // strings to uint64.
594 func StringToUint64HookFunc() DecodeHookFunc {
595 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
596 if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 {
597 return data, nil
598 }
599
600 // Convert it by parsing
601 u64, err := strconv.ParseUint(data.(string), 0, 64)
602 return uint64(u64), wrapStrconvNumError(err)
603 }
604 }
605
606 // StringToIntHookFunc returns a DecodeHookFunc that converts
607 // strings to int.
608 func StringToIntHookFunc() DecodeHookFunc {
609 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
610 if f.Kind() != reflect.String || t.Kind() != reflect.Int {
611 return data, nil
612 }
613
614 // Convert it by parsing
615 i64, err := strconv.ParseInt(data.(string), 0, 0)
616 return int(i64), wrapStrconvNumError(err)
617 }
618 }
619
620 // StringToUintHookFunc returns a DecodeHookFunc that converts
621 // strings to uint.
622 func StringToUintHookFunc() DecodeHookFunc {
623 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
624 if f.Kind() != reflect.String || t.Kind() != reflect.Uint {
625 return data, nil
626 }
627
628 // Convert it by parsing
629 u64, err := strconv.ParseUint(data.(string), 0, 0)
630 return uint(u64), wrapStrconvNumError(err)
631 }
632 }
633
634 // StringToFloat32HookFunc returns a DecodeHookFunc that converts
635 // strings to float32.
636 func StringToFloat32HookFunc() DecodeHookFunc {
637 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
638 if f.Kind() != reflect.String || t.Kind() != reflect.Float32 {
639 return data, nil
640 }
641
642 // Convert it by parsing
643 f64, err := strconv.ParseFloat(data.(string), 32)
644 return float32(f64), wrapStrconvNumError(err)
645 }
646 }
647
648 // StringToFloat64HookFunc returns a DecodeHookFunc that converts
649 // strings to float64.
650 func StringToFloat64HookFunc() DecodeHookFunc {
651 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
652 if f.Kind() != reflect.String || t.Kind() != reflect.Float64 {
653 return data, nil
654 }
655
656 // Convert it by parsing
657 f64, err := strconv.ParseFloat(data.(string), 64)
658 return f64, wrapStrconvNumError(err)
659 }
660 }
661
662 // StringToBoolHookFunc returns a DecodeHookFunc that converts
663 // strings to bool.
664 func StringToBoolHookFunc() DecodeHookFunc {
665 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
666 if f.Kind() != reflect.String || t.Kind() != reflect.Bool {
667 return data, nil
668 }
669
670 // Convert it by parsing
671 b, err := strconv.ParseBool(data.(string))
672 return b, wrapStrconvNumError(err)
673 }
674 }
675
676 // StringToByteHookFunc returns a DecodeHookFunc that converts
677 // strings to byte.
678 func StringToByteHookFunc() DecodeHookFunc {
679 return StringToUint8HookFunc()
680 }
681
682 // StringToRuneHookFunc returns a DecodeHookFunc that converts
683 // strings to rune.
684 func StringToRuneHookFunc() DecodeHookFunc {
685 return StringToInt32HookFunc()
686 }
687
688 // StringToComplex64HookFunc returns a DecodeHookFunc that converts
689 // strings to complex64.
690 func StringToComplex64HookFunc() DecodeHookFunc {
691 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
692 if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 {
693 return data, nil
694 }
695
696 // Convert it by parsing
697 c128, err := strconv.ParseComplex(data.(string), 64)
698 return complex64(c128), wrapStrconvNumError(err)
699 }
700 }
701
702 // StringToComplex128HookFunc returns a DecodeHookFunc that converts
703 // strings to complex128.
704 func StringToComplex128HookFunc() DecodeHookFunc {
705 return func(f reflect.Type, t reflect.Type, data any) (any, error) {
706 if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 {
707 return data, nil
708 }
709
710 // Convert it by parsing
711 c128, err := strconv.ParseComplex(data.(string), 128)
712 return c128, wrapStrconvNumError(err)
713 }
714 }
715