Preflight Checklist
- [X] I have searched the issue tracker for an issue that matches the one I want to file, without success.
- [X] I am not looking for support or already pursued the available support channels without success.
- [X] I have checked the troubleshooting guide for my problem, without success.
Viper Version
1.19.0
Go Version
1.22.0
Config Source
Files
Format
JSON
Repl.it link
No response
Code reproducing the issue
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
var v *viper.Viper
func main() {
initViperConfig()
initViperConfigByNew()
viperSet("example.a", "123")
viperSet("example.b", "123")
viperSet("example.c", "123")
vSet("example.a", "123")
vSet("example.b", "123")
vSet("example.c", "123")
}
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
viper.SetDefault(key, value)
}
err = viper.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
viper.WatchConfig()
}
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用
func initViperConfigByNew() {
v = nil
v = viper.New()
v.SetConfigName("configNew")
v.SetConfigType("json")
v.AddConfigPath(".")
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
v.SetDefault(key, value)
}
err = v.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
v.WatchConfig()
}
func viperSet(key string, value interface{}) {
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
}
viper.Set(key, nil)
}
func vSet(key string, value interface{}) {
v.Set(key, value)
if err := v.WriteConfig(); err != nil {
fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error())
}
v.Set(key, nil)
}
Expected Behavior
After executing the above code, the corresponding configuration file will be generated (if there is already a corresponding configuration file, please delete it before executing the code), and it should directly contain the following content:
{
"example": {
"a": "123",
"b": "123",
"c": "123"
},
"example_key": "example_value"
}
Actual Behavior
If no issues are found, please delete the corresponding configuration file and execute it several more times, as sometimes it may only contain part of the content (missing one or two items), such as:
{
"example": {
"b": "123",
"c": "123"
},
"example_key": "example_value"
}
Steps To Reproduce
- Ensure the configuration file is newly generated when the code is executed (if this is not the first execution, please delete the previously generated corresponding configuration file).
- Execute the code.
Additional Information
It seems that during the Set() process, other content at the same level was accidentally overwritten or deleted, or after setting to nil, it was not immediately detected, resulting in the corresponding key being incorrectly deleted during the next file write. In short, I don’t quite understand the working principles of Viper, but the above-mentioned bug phenomenon does exist.
Comment From: LuSrackhall
Of course, if the project is not very sensitive to performance requirements, you can use this temporary solution like I did. Although this is how I solved the problem, I don’t think it’s the best solution.
package main
import (
"fmt"
"log"
"time"
"github.com/spf13/viper"
)
var v *viper.Viper
func main() {
initViperConfig()
initViperConfigByNew()
viperSet("example.a", "123")
viperSet("example.b", "123")
viperSet("example.c", "123")
vSet("example.a", "123")
vSet("example.b", "123")
vSet("example.c", "123")
}
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
viper.SetDefault(key, value)
}
err = viper.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
viper.WatchConfig()
}
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用
func initViperConfigByNew() {
v = nil
v = viper.New()
v.SetConfigName("configNew")
v.SetConfigType("json")
v.AddConfigPath(".")
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
v.SetDefault(key, value)
}
err = v.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
v.WatchConfig()
}
func viperSet(key string, value interface{}) {
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
}
viper.Set(key, nil)
// 等待 viper.WatchConfig 监听真实配置
time.Sleep(time.Millisecond * 100)
}
func vSet(key string, value interface{}) {
v.Set(key, value)
if err := v.WriteConfig(); err != nil {
fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error())
}
v.Set(key, nil)
// 等待 v.WatchConfig 监听真实配置
time.Sleep(time.Millisecond * 100)
}
Comment From: LuSrackhall
This is my latest temporary solution, which can minimize any unnecessary waiting time during write operations. It also introduces a protective maximum wait time exit mechanism to avoid the occurrence of permanent waiting caused by actual Set("key", nil)
actions.
tips: I suddenly realized that when using Viper, it seems difficult to easily delete an existing configuration item. (Of course, I will discuss this in a newly created issue.)
Before trying, chatGPT told me that although Viper does not directly provide an API to delete a configuration item, it can be indirectly deleted by using
Set("key", nil)
. However, I now find that this method does not seem to work -- it still doesn’t work even after cancelingWatchConfig()
.
package main
import (
"fmt"
"log"
"time"
"github.com/spf13/viper"
)
var v *viper.Viper
func main() {
initViperConfig()
initViperConfigByNew()
viperSet("example.a", "123")
viperSet("example.b", "123")
viperSet("example.c", "123")
viperSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。
viperSet("example.d", nil)
vSet("example.a", "123")
vSet("example.b", "123")
vSet("example.c", "123")
vSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。
vSet("example.d", nil)
for {
}
}
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
viper.SetDefault(key, value)
}
err = viper.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
viper.WatchConfig()
}
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用
func initViperConfigByNew() {
v = nil
v = viper.New()
v.SetConfigName("configNew")
v.SetConfigType("json")
v.AddConfigPath(".")
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
v.SetDefault(key, value)
}
err = v.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
v.WatchConfig()
}
func viperSet(key string, value interface{}) {
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
}
viper.Set(key, nil)
// 等待 viper.WatchConfig 监听真实配置
sleep := true
ch := make(chan (struct{}))
defer close(ch)
go func(sleep *bool, ch chan struct{}) {
defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---config.json")
for {
select {
case <-ch:
fmt.Println("符合预期的退出行为---config.json")
return
case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定
fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---config.json")
*sleep = false
return
}
}
}(&sleep, ch)
for sleep {
if viper.Get(key) != nil {
sleep = false
// 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。
// ch <- struct{}{}
} else {
fmt.Println("阻止了config.json中一次可能存在的错误删除行为")
}
}
}
func vSet(key string, value interface{}) {
v.Set(key, value)
if err := v.WriteConfig(); err != nil {
fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error())
}
v.Set(key, nil)
// 等待 v.WatchConfig 监听真实配置
sleep := true
ch := make(chan (struct{}))
defer close(ch)
go func(sleep *bool, ch chan struct{}) {
defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---configNew.json")
for {
select {
case <-ch:
fmt.Println("符合预期的退出行为---configNew.json")
return
case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定
fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---configNew.json")
*sleep = false
return
}
}
}(&sleep, ch)
for sleep {
if v.Get(key) != nil {
sleep = false
// 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。
// ch <- struct{}{}
} else {
fmt.Println("阻止了configNew.json中一次可能存在的错误删除行为")
}
}
}
Comment From: LuSrackhall
This is my latest temporary solution, which can minimize any unnecessary waiting time during write operations. It also introduces a protective maximum wait time exit mechanism to avoid the occurrence of permanent waiting caused by actual
Set("key", nil)
actions.tips: I suddenly realized that when using Viper, it seems difficult to easily delete an existing configuration item. (Of course, I will discuss this in a newly created issue.)
Before trying, chatGPT told me that although Viper does not directly provide an API to delete a configuration item, it can be indirectly deleted by using
Set("key", nil)
. However, I now find that this method does not seem to work -- it still doesn’t work even after cancelingWatchConfig()
.```go package main
import ( "fmt" "log" "time"
"github.com/spf13/viper" )
var v *viper.Viper
func main() { initViperConfig() initViperConfigByNew()
viperSet("example.a", "123") viperSet("example.b", "123") viperSet("example.c", "123") viperSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。 viperSet("example.d", nil)
vSet("example.a", "123") vSet("example.b", "123") vSet("example.c", "123") vSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。 vSet("example.d", nil)
for { } }
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用 func initViperConfig() { viper.SetConfigName("config") viper.SetConfigType("json") viper.AddConfigPath(".") err := viper.ReadInConfig() if err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // 配置文件不存在,创建默认配置 defaultConfig := map[string]interface{}{ // 在这里添加默认配置项 "example_key": "example_value", } for key, value := range defaultConfig { viper.SetDefault(key, value) } err = viper.SafeWriteConfig() if err != nil { log.Printf("创建配置文件失败: %s\n", err) } else { log.Println("已创建默认配置文件") } } else { log.Printf("读取配置文件失败: %s\n", err) } } viper.WatchConfig() }
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用 func initViperConfigByNew() { v = nil v = viper.New() v.SetConfigName("configNew") v.SetConfigType("json") v.AddConfigPath(".") err := v.ReadInConfig() if err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // 配置文件不存在,创建默认配置 defaultConfig := map[string]interface{}{ // 在这里添加默认配置项 "example_key": "example_value", } for key, value := range defaultConfig { v.SetDefault(key, value) } err = v.SafeWriteConfig() if err != nil { log.Printf("创建配置文件失败: %s\n", err) } else { log.Println("已创建默认配置文件") } } else { log.Printf("读取配置文件失败: %s\n", err) } } v.WatchConfig() }
func viperSet(key string, value interface{}) { viper.Set(key, value) if err := viper.WriteConfig(); err != nil { fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error()) } viper.Set(key, nil) // 等待 viper.WatchConfig 监听真实配置 sleep := true ch := make(chan (struct{})) defer close(ch)
go func(sleep bool, ch chan struct{}) { defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---config.json") for { select { case <-ch: fmt.Println("符合预期的退出行为---config.json") return case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定 fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---config.json") sleep = false return } } }(&sleep, ch)
for sleep { if viper.Get(key) != nil { sleep = false // 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。 // ch <- struct{}{} } else { fmt.Println("阻止了config.json中一次可能存在的错误删除行为") } } }
func vSet(key string, value interface{}) { v.Set(key, value) if err := v.WriteConfig(); err != nil { fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error()) } v.Set(key, nil) // 等待 v.WatchConfig 监听真实配置 sleep := true ch := make(chan (struct{})) defer close(ch)
go func(sleep bool, ch chan struct{}) { defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---configNew.json") for { select { case <-ch: fmt.Println("符合预期的退出行为---configNew.json") return case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定 fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---configNew.json") sleep = false return } } }(&sleep, ch)
for sleep { if v.Get(key) != nil { sleep = false // 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。 // ch <- struct{}{} } else { fmt.Println("阻止了configNew.json中一次可能存在的错误删除行为") } } } ```
1935
Comment From: github-actions[bot]
Issues with no activity for 30 days are marked stale and subject to being closed.