struct2

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 19, 2025 License: MIT Imports: 8 Imported by: 8

README

struct2

License Coverage GitHub Workflow Status Go Report Card Go PKG

This repository helps to work with struct, convert map and get information about that.

This is a modified version of common struct to map libraries with cool features.

Usage

go get github.com/worldline-go/struct2
Map

Get decoder and run Map method, default looking the struct tag in struct.

Supported tags: -, omitempty, string, ptr2, omitnested, flatten, remain.

Convertion order is -, omitempty, string, ptr2, custom hook function, hooker interface, omitnested + flatten

type ColorGroup struct {
    ID     int      `db:"id"`
    Name   string   `db:"name"`
    Colors []string `db:"colors"`
    // custom type with implemented Hooker interface
    // covertion result to time.Time
    Date types.Time `db:"time"`
	// RGB unknown type but to untouch it add omitnested to keep that struct type
	RGB *rgb `db:"rgb,omitempty,omitnested"`
}

//...

// default tagName is `struct`
decoder := struct2.Decoder{
    TagName: "db",
}

// get map[string]any
result := decoder.Map(group)

// or use one line
// result := new(struct2.Decoder).SetTagName("db").Map(group) // default tag name is "struct"

Custom decoder can be use in struct which have struct2.Hooker interface.
Or set a slice of custom struct2.HookFunc functions in decoder.

Check documentation examples.

Tags Information

omitnested: very helpful to don't want to touch data.

ptr2: convert pointer to the concrete value. If pointer is nil, new empty value is generated. ptr2 to effect custom hook functions and hooker interface also omitnested.

remain: Must be defined as map[string]any in struct. Puts all unknown fields, destined for the struct into the remain field.

Decode

Decode is working almostly same as the mitchellh/mapstructure repo.

Default tag is struct in struct.

decoder := struct2.Decoder{
    WeaklyTypedInput: true,
    WeaklyIgnoreSeperator: true,
    TagName: "struct",
    BackupTagName: "json",
}

//...

// input and output could be any, output should be pointer
if err := d.Decode(input, output); err != nil {
    return err
}

Inspired Projects

When starting this project, I want to make it from scratch and I can learn more and make some new features but codes turning to fatih/structs repo and they are solved lots of problems so I copied parts in there and add some features as hook functions. After that I want to extend that one to make map to struct operations. In that time, I see I need to check all types due to mixing types together. So I copied parts in mitchellh/mapstructure. Thanks for all people to make them.

fatih/structs
mitchellh/mapstructure

Documentation

Overview

Example
package main

import (
	"fmt"
	"sort"
	"time"

	"github.com/worldline-go/struct2"
)

func sortPrint(m map[string]any) {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		fmt.Printf("Type: %T, Value: %v\n", m[k], m[k])
	}
}

func main() {
	type ColorGroup struct {
		ID     int        `json:"id"`
		Name   string     `json:"name"`
		Colors []string   `json:"colors"`
		Date   timeCustom `json:"time"`
	}

	d, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")

	group := ColorGroup{
		ID:     1,
		Name:   "Reds",
		Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
		Date:   timeCustom{Time: d},
	}

	result := new(struct2.Decoder).SetTagName("json").Map(group) // default tag name is "struct"

	// fmt.Printf("%#v", result)
	sortPrint(result)
}
Output:

Type: []string, Value: [Crimson Red Ruby Maroon]
Type: int, Value: 1
Type: string, Value: Reds
Type: time.Time, Value: 2006-01-02 15:04:05 +0000 UTC
Example (CustomHook)
package main

import (
	"fmt"
	"reflect"

	"github.com/worldline-go/struct2"
)

func main() {
	type ColorGroup struct {
		Name  string `db:"name"`
		Count int    `db:"count"`
	}

	group := ColorGroup{
		Name: "DeepCore",
	}

	decoder := struct2.Decoder{
		TagName: "db",
		Hooks: []struct2.HookFunc{func(v reflect.Value) (any, error) {
			if v.Kind() == reflect.String {
				return "str_" + v.Interface().(string), nil
			}

			return nil, struct2.ErrContinueHook
		}},
	}

	result := decoder.Map(group)

	fmt.Printf("%v", result["name"])
}
Output:

str_DeepCore
Example (MapToStruct)
package main

import (
	"fmt"
	"reflect"
	"time"

	"github.com/worldline-go/struct2"
)

func main() {
	type SubCfg struct {
		Enabled bool `cfg:"enabled"`
	}

	type Config struct {
		Name  string        `cfg:"name"`
		Count int           `cfg:"count"`
		Sub   SubCfg        `cfg:"sub"`
		Time  time.Duration `cfg:"time"`
	}

	decoder := struct2.Decoder{
		TagName: "cfg",
		HooksDecode: []struct2.HookDecodeFunc{func(in reflect.Type, out reflect.Type, data any) (any, error) {
			if out == reflect.TypeFor[time.Duration]() {
				switch in.Kind() {
				case reflect.String:
					return time.ParseDuration(data.(string))
				}
			}

			return data, nil
		}},
		WeaklyTypedInput:      true,
		WeaklyIgnoreSeperator: true,
		WeaklyDashUnderscore:  true,
	}

	cfgMap := map[string]any{
		"name":  "Altay",
		"count": "42",
		"sub": map[string]any{
			"enabled": "true",
		},
		"time": "1h30m",
	}

	var cfg Config

	if err := decoder.Decode(cfgMap, &cfg); err != nil {
		fmt.Printf("decode error: %v", err)
		return
	}

	fmt.Printf("%v %v %v %v", cfg.Name, cfg.Count, cfg.Sub.Enabled, cfg.Time)
}
Output:

Altay 42 true 1h30m0s

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrContinueHook = errors.New("continue to decode")

ErrContinueHook usable with HookFunc. This error type not checking by decode. Replacable by any error to continue to decode.

Functions

func Ptr2Concrete

func Ptr2Concrete(val any) any

func WithOmitNested added in v1.4.0

func WithOmitNested() optionMap

Types

type Decoder

type Decoder struct {
	// Tagname to lookup struct's field tag, default is `struct`
	TagName string
	// Hooks function run before decode and enable to change of data.
	Hooks []HookFunc

	// HooksDecode to modify data before decode.
	HooksDecode []HookDecodeFunc

	// WeaklyTypedInput is true, the decoder will make the following
	// "weak" conversions:
	//
	//   - bools to string (true = "1", false = "0")
	//   - numbers to string (base 10)
	//   - bools to int/uint (true = 1, false = 0)
	//   - strings to int/uint (base implied by prefix)
	//   - int to bool (true if value != 0)
	//   - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
	//     FALSE, false, False. Anything else is an error)
	//   - empty array = empty map and vice versa
	//   - negative numbers to overflowed uint values (base 10)
	//   - slice of maps to a merged map
	//   - single values are converted to slices if required. Each
	//     element is weakly decoded. For example: "4" can become []int{4}
	//     if the target type is an int slice.
	//
	WeaklyTypedInput bool

	// ZeroFields, if set to true, will zero fields before writing them.
	// For example, a map will be emptied before decoded values are put in
	// it. If this is false, a map will be merged.
	ZeroFields bool

	// Squash will squash embedded structs.  A squash tag may also be
	// added to an individual struct field using a tag.  For example:
	//
	//  type Parent struct {
	//      Child `struct:",squash"`
	//  }
	Squash bool
	// IgnoreUntaggedFields ignores all struct fields without explicit
	// TagName, comparable to `struct:"-"` as default behaviour.
	IgnoreUntaggedFields bool

	// BackupTagName usable if TagName not found.
	BackupTagName string

	// WeaklyDashUnderscore apply underscore/dash conversion to variables
	// on map to struct. variable_name == variable-name.
	WeaklyDashUnderscore bool

	// WeaklyIgnoreSeperator ignore seperator on map to struct. variable_name == variablename
	// values are -, _ and space.
	WeaklyIgnoreSeperator bool

	// ForcePtr2 assume `struct:",ptr2` on all pointer struct fields.
	ForcePtr2 bool

	// OmitNullPtr omits nil pointers, in the map.
	OmitNilPtr bool

	// OutputCamelCase will convert map keys to camel case.
	OuputCamelCase bool

	// NoRemainFields will fail if there are extra fields in the input, that are not in the output.
	NoRemainFields bool
}

Decoder is main struct of struct2, holds config and functions.

func (*Decoder) Decode added in v1.2.1

func (d *Decoder) Decode(input, output any) error

Decode the interface to another.

func (*Decoder) GetFields

func (d *Decoder) GetFields(s any) []string

func (*Decoder) Map

func (d *Decoder) Map(input any, opts ...optionMap) map[string]any

Map converts given struct to the map[string]any. Panic if input not a struct type.

func (*Decoder) MapOmitNested deprecated

func (d *Decoder) MapOmitNested(input any) map[string]any

MapOmitNested converts given struct to the map[string]any omitting nested structs and maps.

Deprecated: use Map with WithOmitNested option instead.

func (*Decoder) MapSlice added in v1.4.0

func (d *Decoder) MapSlice(input any, opts ...optionMap) []map[string]any

MapSlice converts given slice of structs to []map[string]any. Panic if input not a slice or array type.

func (*Decoder) SetHooks added in v1.1.0

func (d *Decoder) SetHooks(hooks []HookFunc) *Decoder

func (*Decoder) SetHooksDecode added in v1.2.2

func (d *Decoder) SetHooksDecode(hooksDecode []HookDecodeFunc) *Decoder

func (*Decoder) SetTagName added in v1.1.0

func (d *Decoder) SetTagName(t string) *Decoder

type HookDecodeFunc added in v1.2.2

type HookDecodeFunc func(reflect.Type, reflect.Type, any) (any, error)

HookDecodeFunc get input, output and data and return modified data.

type HookFunc

type HookFunc func(reflect.Value) (any, error)

HookFunc get reflect.Value to modify custom in decoder.

type Hooker

type Hooker interface {
	Struct2Hook() any
}

Hooker interface for structs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL