Closed atc0005 closed 2 years ago
go-toml v2 did change the default behavior of unmarshalling embedded fields (see Embedded structs behave like stdlib). It follows stdlib's encoding/json behavior. In short, embedded structs' fields are rolled up into the outer struct unless the embedded struct has an explicit name using the toml
tag. For example, taking a shortened version of your example:
package main
import (
"fmt"
"github.com/pelletier/go-toml/v2"
)
type FileHandling struct {
FilePattern string `toml:"pattern"`
}
type Config struct {
FileHandling `toml:"filehandling"` // <--- the tag to add
}
func main() {
var defaultConfigFile = []byte(`
[filehandling]
pattern = "reach-masterdev-"`)
tomlv2Config := Config{}
if err := toml.Unmarshal(defaultConfigFile, &tomlv2Config); err != nil {
fmt.Printf("Failed to unmarshal TOML config: %v\n", err)
}
// ...
}
This should work as expected. However, while testing this, I did find a bug in the unmarshal code that deals with anonymous structs. I fixed it with https://github.com/pelletier/go-toml/commit/c5ca2c682b572078ab6242cd90c6328b934dc319. If you use that commit and add the toml
tags to the external fields it should work as you expect. Let me know if it doesn't!
@pelletier Thank you for your detailed reply and for pointing me at the relevant section of the README.
I've tested the changes you described and they work without issue.
Thank you for your time and for this library!
Glad it worked!
Describe the bug
v1 supported unmarshaling a TOML config into a struct with embedded struct fields. v2 does not seem to support this.
To Reproduce
Prototype to emulate issue
```golang package main import ( "fmt" tomlv1 "github.com/pelletier/go-toml" tomlv2 "github.com/pelletier/go-toml/v2" ) type AppMetadata struct { AppName string `toml:"-" arg:"-"` AppDescription string `toml:"-" arg:"-"` AppVersion string `toml:"-" arg:"-"` AppURL string `toml:"-" arg:"-"` } type FileHandling struct { FilePattern *string `toml:"pattern"` FileExtensions []string `toml:"file_extensions"` FileAge *int `toml:"file_age"` NumFilesToKeep *int `toml:"files_to_keep"` KeepOldest *bool `toml:"keep_oldest"` Remove *bool `toml:"remove"` IgnoreErrors *bool `toml:"ignore_errors"` } type Search struct { Paths []string `toml:"paths"` RecursiveSearch *bool `toml:"recursive_search"` } type Logging struct { LogLevel *string `toml:"log_level"` LogFormat *string `toml:"log_format"` LogFilePath *string `toml:"log_file_path"` ConsoleOutput *string `toml:"console_output"` UseSyslog *bool `toml:"use_syslog"` } // Config represents a collection of configuration settings for this // application. Config is created as early as possible upon application // startup. type Config struct { // Embed other structs in an effort to better group related settings AppMetadata FileHandling Logging Search // Path to (optional) configuration file ConfigFile *string `toml:"config_file"` } func main() { // Mock on-disk config file that will be parsed by pelletier/go-toml // library. var defaultConfigFile = []byte(` [filehandling] pattern = "reach-masterdev-" file_extensions = [ ".war", ".tmp", ] file_age = 1 files_to_keep = 2 keep_oldest = false remove = false ignore_errors = true [search] paths = [ "/tmp/elbow/path1", "/tmp/elbow/path2", ] recursive_search = true [logging] log_level = "debug" log_format = "text" # If set, all output to the console will be muted and sent here instead log_file_path = "/tmp/log.json" console_output = "stdout" use_syslog = false`) defaultConfigFilePath := "" tomlv1Config := Config{ ConfigFile: &defaultConfigFilePath, } if err := tomlv1.Unmarshal(defaultConfigFile, &tomlv1Config); err != nil { fmt.Printf("Failed to unmarshal TOML config: %v\n", err) } fmt.Printf("\ntomlv1Config settings: %+v\n", tomlv1Config) tomlv2Config := Config{ ConfigFile: &defaultConfigFilePath, } if err := tomlv2.Unmarshal(defaultConfigFile, &tomlv2Config); err != nil { fmt.Printf("Failed to unmarshal TOML config: %v\n", err) } fmt.Printf("\ntomlv2Config settings: %+v\n", tomlv2Config) } ```Expected behavior
Replace v1 with v2 and existing use case continues to work.
Versions
Additional context
I've reviewed the list of changes for the v2 release, but honestly I don't understand the details well enough to know whether this particular use case was dropped from v2.