waveterm/pkg/util/utilfn/marshal.go
Mike Sawka 91c293e4be
VDom 8 (#1202)
* new vdomevents to support click, change, keydown, etc. easier type
signature
* can now pass a prop type instead of always converting to
map[string]any
* implement DefineComponent to allow easier vdom creation using a
component function directly
* separate vdomclient Make from Connect
* lots of bug fixes to get everything working again
* PStyle and special "style" attribute handling
2024-11-04 12:52:36 -08:00

146 lines
3.2 KiB
Go

// 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())
}