Problem description The TOML spec defines tables but it doesn't seem to mandate that a table has to have a key-value pair associated with it. Apparently (after a quick skim of the spec), this looks valid:
[emptytable]
[someother]
key="value"
The way AllKeys()
recursively goes through the entries, empty tables are skipped. (AllSettings()
even relies on this.) I only get someother.key
back but not emptytable
.
User story: I need a way to get all table names in a TOML file, including the empty tables. The empty table name has a special meaning in my code and currently, there is no way to find all empty tables. (The config file is dynamic, so I can't foresee the names that come up.)
Expected behavior (what you expected to happen):
AllKeys()
return empty table names. (Above example: emptytable
and someother.key
.)
Since AllSettings()
does an explicit check for empty keys already, this should not have an impact on the code. But it might in other cases, so alternatively an AllKeysIncludingEmpty
or similar additional function would be acceptable.
(Yet another solution I was trying is to just parse Viper.config but it's not public.)
Actual behavior (what actually happened):
AllKeys()
returns someother.key
but not emptytables
.
Repl.it link: https://replit.com/@gregszabo/TOMLExample
Code reproducing the issue:
package main
import (
"bytes"
"fmt"
"github.com/spf13/viper"
)
func main() {
var tomlExample = []byte(`
[emptytable]
[someother]
key="value"
`)
viper.SetConfigType("toml")
viper.ReadConfig(bytes.NewBuffer(tomlExample))
fmt.Printf("%v\n",viper.AllKeys())
}
Output:
[someother.key]
Environment: - Viper version: 1.7.1
Anything else we should know?:
Comment From: github-actions[bot]
👋 Thanks for reporting!
A maintainer will take a look at your issue shortly. 👀
In the meantime: We are working on Viper v2 and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.
⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9
📣 If you've already given us your feedback, you can still help by spreading the news, either by sharing the above link or telling people about this on Twitter:
https://twitter.com/sagikazarmark/status/1306904078967074816
Thank you! ❤️
Comment From: greg-szabo
Oh, shoot, I missed the one-year anniversary of this issue. Happy anniversary!
Comment From: sagikazarmark
Honestly, it's hard to decide whether this is a bug or not. TOML parses an empty table into a map which tells Viper to accept either leaf values or further maps within. From Viper's perspective, an empty map is not a value, it's just a non-leaf node in the tree of values.
Comment From: psihachina
In our project, it is also necessary to get empty tables. I dig into the code and suggested that such a solution could help:
func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
if shadow != nil && prefix != "" && shadow[prefix] {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]bool)
}
var m2 map[string]interface{}
if prefix != "" {
prefix += v.keyDelim
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = true
continue
}
//SOLUTION
if v.SomeOption {
if len(val.(map[string]interface{})) == 0 {
shadow[strings.ToLower(fullKey)] = true
continue
}
}
// recursively merge to shadow map
shadow = v.flattenAndMergeMap(shadow, m2, fullKey)
}
return shadow
}
Comment From: github-actions[bot]
Issues with no activity for 30 days are marked stale and subject to being closed.