github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/fields/02_reflect/scan.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"strings"
     9  )
    10  
    11  //gistsnip:start:unmarshal
    12  func Unmarshal(rd io.Reader, data interface{}) error {
    13  	var config jsonConfig
    14  	err := json.NewDecoder(rd).Decode(&config)
    15  	if err != nil {
    16  		return err
    17  	}
    18  
    19  	return config.Scan(data)
    20  }
    21  
    22  type jsonConfig struct {
    23  	Fields []jsonField
    24  }
    25  
    26  type jsonField struct {
    27  	Name       string
    28  	Type       string
    29  	Val        interface{}
    30  	Multiplier interface{}
    31  }
    32  
    33  //gistsnip:end:unmarshal
    34  
    35  //gistsnip:start:scan
    36  func (config *jsonConfig) Scan(r interface{}) error {
    37  	// check that r is a pointer to some struct
    38  	rv := reflect.ValueOf(r)
    39  	if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
    40  		return fmt.Errorf("expected pointer to a struct, got %T", r)
    41  	}
    42  
    43  	s := rv.Elem()
    44  	t := s.Type()
    45  
    46  	// iterate over all struct fields
    47  	for i, n := 0, s.NumField(); i < n; i++ {
    48  		resultField := s.Field(i)
    49  
    50  		// find the corresponding field from config
    51  		field, err := config.findField(t.Field(i).Name)
    52  		if err != nil {
    53  			return err
    54  		}
    55  
    56  		// assign field value to the struct field
    57  		err = config.assignField(field, resultField.Addr().Interface())
    58  		if err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  //gistsnip:end:scan
    67  
    68  func (config *jsonConfig) findField(name string) (*jsonField, error) {
    69  	for i := 0; i < len(config.Fields); i++ {
    70  		field := &config.Fields[i]
    71  		if strings.EqualFold(field.Name, name) {
    72  			return field, nil
    73  		}
    74  	}
    75  	return nil, fmt.Errorf("unable to find field " + name)
    76  }
    77  
    78  //gistsnip:start:assignField
    79  func (config *jsonConfig) assignField(field *jsonField, p interface{}) error {
    80  	// p is a pointer to struct field
    81  	switch p := p.(type) {
    82  	case *uint:
    83  		uv, ok := field.Val.(float64)
    84  		if !ok || field.Type != "uint" {
    85  			return fmt.Errorf("expected uint, got %T and %v", field.Val, field.Type)
    86  		}
    87  		*p = uint(uv)
    88  	case *float64:
    89  		uv, ok := field.Val.(float64)
    90  		if !ok || field.Type != "float" {
    91  			return fmt.Errorf("expected float, got %T and %v", field.Val, field.Type)
    92  		}
    93  		*p = uv
    94  	default:
    95  		return fmt.Errorf("unhandled field type %T", p)
    96  	}
    97  	return nil
    98  }
    99  
   100  //gistsnip:end:assignField