// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 package utilfn import ( "encoding/json" "fmt" "reflect" "strings" "github.com/mitchellh/mapstructure" ) func ReUnmarshal(out any, in any) error { barr, err := json.Marshal(in) if err != nil { return err } return json.Unmarshal(barr, out) } // does a mapstructure using "json" tags func DoMapStructure(out any, input any) error { dconfig := &mapstructure.DecoderConfig{ Result: out, TagName: "json", } decoder, err := mapstructure.NewDecoder(dconfig) if err != nil { return err } return decoder.Decode(input) } func MapToStruct(in map[string]any, out any) error { // Check that out is a pointer outValue := reflect.ValueOf(out) if outValue.Kind() != reflect.Ptr { return fmt.Errorf("out parameter must be a pointer, got %v", outValue.Kind()) } // Get the struct it points to elem := outValue.Elem() if elem.Kind() != reflect.Struct { return fmt.Errorf("out parameter must be a pointer to struct, got pointer to %v", elem.Kind()) } // Get type information typ := elem.Type() // For each field in the struct for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) // Skip unexported fields if !field.IsExported() { continue } name := getJSONName(field) if value, ok := in[name]; ok { if err := setValue(elem.Field(i), value); err != nil { return fmt.Errorf("error setting field %s: %w", name, err) } } } return nil } func StructToMap(in any) (map[string]any, error) { // Get value and handle pointer val := reflect.ValueOf(in) if val.Kind() == reflect.Ptr { val = val.Elem() } // Check that we have a struct if val.Kind() != reflect.Struct { return nil, fmt.Errorf("input must be a struct or pointer to struct, got %v", val.Kind()) } // Get type information typ := val.Type() out := make(map[string]any) // For each field in the struct for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) // Skip unexported fields if !field.IsExported() { continue } name := getJSONName(field) out[name] = val.Field(i).Interface() } return out, nil } // getJSONName returns the field name to use for JSON mapping func getJSONName(field reflect.StructField) string { tag := field.Tag.Get("json") if tag == "" || tag == "-" { return field.Name } return strings.Split(tag, ",")[0] } // setValue attempts to set a reflect.Value with a given interface{} value func setValue(field reflect.Value, value any) error { if value == nil { return nil } valueRef := reflect.ValueOf(value) // Direct assignment if types are exactly equal if valueRef.Type() == field.Type() { field.Set(valueRef) return nil } // Check if types are assignable if valueRef.Type().AssignableTo(field.Type()) { field.Set(valueRef) return nil } // If field is pointer and value isn't already a pointer, try address if field.Kind() == reflect.Ptr && valueRef.Kind() != reflect.Ptr { return setValue(field, valueRef.Addr().Interface()) } // Try conversion if types are convertible if valueRef.Type().ConvertibleTo(field.Type()) { field.Set(valueRef.Convert(field.Type())) return nil } return fmt.Errorf("cannot set value of type %v to field of type %v", valueRef.Type(), field.Type()) }