Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading a YAML file makes all keys lowercase, but YAML keys are case sensitive. #260

Open
barsanuphe opened this issue Oct 14, 2016 · 31 comments

Comments

@barsanuphe
Copy link

Since the recent changes, all keys have become lowercase.
If I'm not mistaken (see this reply from YAML spec author), YAML keys are case sensitive.
For instance, calling GetStringMapStringSlice() while loading a YAML file makes all of the keys of the resulting map lowercase, which is unexpected and was not the previous behavior.

@bep
Copy link
Collaborator

bep commented Oct 14, 2016

GetStringMapStringSlice() while loading a YAML file

When do you call that method while loading a YAML file?

@barsanuphe
Copy link
Author

barsanuphe commented Oct 14, 2016

If you have:

yaml_key:
         Case Sensitive Key: 
               - value1
               - value2

GetStringMapStringSlice("yaml_key") now gives a map with a case sensitive key key.

@bep
Copy link
Collaborator

bep commented Oct 14, 2016

To conclude: Viper have always tried to be case-insensitive by doing a lower-casing of the keys given. It was however not being doing that deeply for maps. Now that is fixed and we are seeing some breakage in the wild.

The main problem seems to be the expectation that you should be able to unmarshal the config map into a struct or similar.

Because for plain lookups, like viper.Get("a.b.A.d."), this is clearly an improvement.

@spf13 should chime in here.

@bep
Copy link
Collaborator

bep commented Oct 14, 2016

GetStringMapStringSlice("yaml_key") now gives a map with a "case sensitive key" key.

No, it gives a lower-cased key.

@barsanuphe
Copy link
Author

No, it gives a lower-cased key.

Yes, I meant it gives the value: case sensitive key in my example.

I understand your point of view that this is the intended behavior, but I find it surprising for YAML. And I'm not expecting to unmarshall the config map into anything, I'm happy dealing with a map[string][]string. I was just relying on the keys to have the same case I put in the YAML file.

My "top" keys are lowercase though, so I didn't realize viper was already returning lowercased versions for anything but maps.

@bep
Copy link
Collaborator

bep commented Oct 14, 2016

I understand your situation -- I have a similar breakage in one of my apps. We should probably have handled this in a better way, but I believe it is the intended behavior of Viper from the start. @spf13 should decide.

@spf13
Copy link
Owner

spf13 commented Oct 14, 2016

The way I think it should work is a bit of a hybrid of case sensitivity.

Keys should be treated as case insensitive when getting, setting or
merging. I can't see another approach that would make sense here.

Returning keys would ideally be in the case originally defined.

The challenge here is that while yaml is case sensitive, not all of the
configuration options are (flags, env vars, etc). Even Go itself has odd
sensitivity behaviors as the initial letter is loaded with exportability.

On Fri, Oct 14, 2016 at 7:06 PM Bjørn Erik Pedersen <
notifications@github.com> wrote:

I understand your situation -- I have a similar breakage in one of my
apps. We should probably have handled this in a better way, but I believe
it is the intended behavior of Viper from the start. @spf13
https://github.com/spf13 should decide.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#260 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAKlZFU7xGn_CZg1KReWeJ9rlKudEy1nks5q0ArYgaJpZM4KXgSN
.

@bep
Copy link
Collaborator

bep commented Oct 14, 2016

Returning keys would ideally be in the case originally defined.

Which is a very expensive requirement, and in many cases not possible.

As I said earlier, this change was pushed earlier but then I did a revert. Viper is getting pretty complex -- it would be nice to have a simple and agreeable understanding of the casing of strings.

@awfm9
Copy link

awfm9 commented Oct 16, 2016

I am not familiar with many configuration use cases and the requirements for case sensitive keys, but if I can chime in, I really think the consistent conversion to lower case keys is the way to go. Feel free to point out some cases that I have no vision on, but I don't really see an upside to complicating the code with some hybrid handling.

@bep
Copy link
Collaborator

bep commented Oct 18, 2016

I agree with @awishformore -- I suggest we just ride off the storm.

@dgoeke
Copy link

dgoeke commented Jan 3, 2017

I am not familiar with many configuration use cases and the requirements for case sensitive keys

Our application uses viper for describing environment variables in a YAML file:

environment:
  FIRST_THING: yes
  secondThing: no

These could have arbitrary names/values since the user defines them. But environment variables are case sensitive, so this means we can no longer represent them naturally as a map. I suppose we will have to switch to something like a list of key=value if we want to continue using viper.

aidansteele added a commit to glassechidna/stackit that referenced this issue May 26, 2017
@gabdelab
Copy link

gabdelab commented Aug 7, 2017

Hi, to follow up on this issue, I encountered a bug with my configuration, with some Keys in lists and some other unlisted:

package main

import (
	"bytes"
	"fmt"
	"github.com/spf13/viper"
)

var yamlExample = []byte(`
Unlisted:
    Test: true
Listed:
    - Test: true
`)

func main() {
	viper.SetConfigType("yaml")
	viper.ReadConfig(bytes.NewBuffer(yamlExample))

	// Will output "test:true"
	var a interface{}
	viper.UnmarshalKey("Unlisted", &a)
	fmt.Println(a)

	// Will output "Test:true"
	var b interface{}
	viper.UnmarshalKey("Listed", &b)
	fmt.Println(b)
}

Shouldn't all keys be treated the same way ? These two different behaviors look quite error-prone to me.

@wminjay
Copy link

wminjay commented Sep 8, 2017

It seems to be intentional

func insensitiviseMap(m map[string]interface{}) {
	for key, val := range m {
		switch val.(type) {
		case map[interface{}]interface{}:
			// nested map: cast and recursively insensitivise
			val = cast.ToStringMap(val)
			insensitiviseMap(val.(map[string]interface{}))
		case map[string]interface{}:
			// nested map: recursively insensitivise
			insensitiviseMap(val.(map[string]interface{}))
		}

		lower := strings.ToLower(key)
		if key != lower {
			// remove old key (not lower-cased)
			delete(m, key)
		}
		// update map
		m[lower] = val
	}
}

@anasanzari
Copy link

+1

puiterwijk added a commit to repoSpanner/repoSpanner that referenced this issue Sep 20, 2018
It seems that viper lowercases keys, even if we just get a
map[string]string back, which breaks binds and symlinks with
case sensitive mount points with uppercase in them.

Instead, just go for this ugly, but working, lists-of-lists
type.

spf13/viper#260

Signed-off-by: Patrick Uiterwijk <patrick@puiterwijk.org>
@rwxrob
Copy link

rwxrob commented Dec 28, 2018

For what it is worth I recently discovered that the go-yaml yaml.Marshal() method automatically forces names to lowercase unless they are explicitly tagged, which I personally find really annoying. I was expecting the same json.Marshal() behavior (as many others have as well):

type Position struct {
	Row int
	Col int
	Byt int
	SrC string `yaml:",omitempty"`
}

func (p Position) YAML() string {
	byt, err := yaml.Marshal(p)
	if err != nil {
		return ""
	}
	return string(byt)
}

func ExamplePosition_YAML() {
	
	p := Position{1,2,3,"foo"}
	fmt.Print(p.YAML())
	
	// Output:
	// Row: 1
        // Col: 2
	// Byt: 3
	// SrC: foo

}
--- FAIL: ExamplePosition_YAML (0.00s)
got:
row: 1
col: 2
byt: 3
src: foo
want:
Row: 1
Col: 2
Byt: 3
SrC: foo
FAIL

The only way around it is to explicitly tag them the way you want them:

type Position struct {
	Row int    `yaml:"Row"`
	Col int    `yaml:"Col"`
	Byt int    `yaml:"Byt"`
	SrC string `yaml:"SrC,omitempty"`
}

Which gets doubly annoying if you want to json.Marshal() as well. (I have pos.String(), pos.JSON(), and pos.YAML()):

type Position struct {
	Row int    `json:"Row" yaml:"Row"`
	Col int    `json:"Col" yaml:"Col"`
	Byt int    `json:"Byt" yaml:"Byt"`
	SrC string `json:"SrC,omitempty" yaml:"SrC,omitempty"`
}

Of course adding TOML to the mix would get really fun. I really like the idea of being able to represent structures in multiple formats, but this can get tedious fast.

Which led me to research what the Kubernetes folks are doing because they have such a massive dependency on YAML and JSON working in what appears to be a case-sensitive way.

How Kubernetes Does It

First of all, under the hood k8s use https://github.com/ghodss/yaml to get rid of YAML marshaling tags altogether even thought it uses the same go-yaml module.

Second, they use the explicit name in the struct tags:

./staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go: APIVersion string `json:"apiVersion,omitempty"`

Viper Thwarted by go-yaml?

I'm betting viper is facing this issue because of the implied names from marshaling structs that are not explicitly named with tags. As long as go-yaml is being used (which is a port of a C lib) this will likely remain.

Anyone feel like writing a Go YAML library that actually respects the same marshaling naming tags like JSON does? I bet a fork of go-yaml could do it. 😁

@dylanlive
Copy link

+1 for adding a setting to preserve casing.

I wanted to document my use case:
My app looks up EC2 Instances by tags, but ec2 tags are case sensitive in the aws sdk. I was hoping to store my tags in a yaml file, like so:

tags:
  Service: myapp
  Owner: Dylan
  Environment: dev

Due to this, I'm not going to be able to use viper.

knadh added a commit to knadh/viper that referenced this issue Jan 25, 2019
YAML, TOML, and JSON dictate keys to be case-sensitive. Viper's default
behaviour of lowercasing the keys for key insensitivity is incompatible
with these standards and has the side effect of making it difficult for
use cases such as case sensitive API credentials in configuration.
For eg: MyApiKey=MySecret (in TOML).

See spf13#131, spf13#260, spf13#293, spf13#371, spf13#373

This commit adds a global function `viper.SetKeyCaseSensitivity()` that
enables this behaviour to be turned off, after which, all keys, irrespective
of nesting, retain their cases. This respects all configuration operations
including getting, setting, and merging.
knadh added a commit to knadh/viper that referenced this issue Jan 29, 2019
YAML, TOML, and JSON dictate keys to be case-sensitive. Viper's default
behaviour of lowercasing the keys for key insensitivity is incompatible
with these standards and has the side effect of making it difficult for
use cases such as case sensitive API credentials in configuration.
For eg: MyApiKey=MySecret (in TOML).

See spf13#131, spf13#260, spf13#293, spf13#371, spf13#373

This commit adds a global function `viper.SetKeyCaseSensitivity()` that
enables this behaviour to be turned off, after which, all keys, irrespective
of nesting, retain their cases. This respects all configuration operations
including getting, setting, and merging.
knadh added a commit to knadh/viper that referenced this issue Jan 29, 2019
YAML, TOML, and JSON dictate keys to be case-sensitive. Viper's default
behaviour of lowercasing the keys for key insensitivity is incompatible
with these standards and has the side effect of making it difficult for
use cases such as case sensitive API credentials in configuration.
For eg: MyApiKey=MySecret (in TOML).

See spf13#131, spf13#260, spf13#293, spf13#371, spf13#373

This commit adds a global function `viper.SetKeysCaseSensitive()` that
enables this behaviour to be turned off, after which, all keys, irrespective
of nesting, retain their cases. This respects all configuration operations
including getting, setting, and merging.
This was referenced Aug 30, 2019
@sgotliv
Copy link

sgotliv commented Sep 29, 2019

This problem seems to be fixed already, but I don't see new releases since end of May 2019. Does anyone know when next release is scheduled?

@sagikazarmark sagikazarmark added this to the 1.5.0 milestone Oct 14, 2019
@sagikazarmark
Copy link
Collaborator

I added it to the 1.5 milestone. New release will happen soon.

@sagikazarmark
Copy link
Collaborator

Releasing 1.5.0

@prymitive
Copy link

Can someone give a tl;dr summary of the solution present in 1.5.0?
Github release only states Documentation and other fixes, would be nice to have a pointer for everyone who visits this page

@sagikazarmark
Copy link
Collaborator

I assumed this has been fixed, but looking through the issue I'm not so sure anymore. I'm reopening the issue until I figure out what's going on.

@sagikazarmark sagikazarmark reopened this Nov 1, 2019
@sagikazarmark sagikazarmark removed this from the 1.5.0 milestone Nov 1, 2019
@twpayne
Copy link

twpayne commented Nov 3, 2019

I confirm that the issue is still present. When using 1.5.0, keys in TOML configuration files (in my case) are still converted to lowercase.

@thousandhu
Copy link

thousandhu commented Jan 20, 2020

This issue also happens in json configuration now. Is there anyone working on it?

@sbourlon
Copy link

sbourlon commented Feb 1, 2020

@twpayne @gbunt If adding viper.SetKeysCaseSensitive() is the agreed solution, what about reopening and merging the PR #758?

EDIT: seems like #758 has been replaced by #635

@twpayne
Copy link

twpayne commented Feb 1, 2020

Thanks for the pointer @sbourlon. From this comment it looks like Viper is not going to get case sensitivity any time soon, if ever.

dmitry-irtegov added a commit to kublr/viper that referenced this issue Mar 2, 2020
Todo: review and adjust tests
storjBuildBot pushed a commit to storj/storj that referenced this issue May 19, 2020
Viper has a feature/bug where all YAML config keys are cast
to lowercase (see spf13/viper#260
and spf13/viper#411).
Since we use Viper to load our config values, it doesn't seem
like there's an easy way to preserve case sensitivity for the
access names chosen by users right now. Adding this prompt
should help user experience by clarifying that all access
names must be lowercase.

Change-Id: I47e8344bb0ca7e78458405496f20e78e3c9f9a88
@kansz
Copy link

kansz commented Jun 2, 2020

If you have:

yaml_key:
         Case Sensitive Key: 
               - value1
               - value2

GetStringMapStringSlice("yaml_key") now gives a map with a case sensitive key key.

not a workaround in 2020/06/02 master branch.
When my yaml like:
iocFilterSchemaForHeader:
<2 blanks>FileSize: [FileSize]
<2 blanks>FileMD5: [FileMD5]
<2 blanks>FileSHA1: [FileSHA1]
<2 blanks>FileSHA256: [FileSHA256]
<2 blanks>PPf: [PPf]
<2 blanks>Family: [Family]
<2 blanks>Score: [Score]
And read it with viper.GetStringMapStringSlice("iocFilterSchemaForHeader"), print it, it like:
get iocFilterSchemaForHeader:map[family:[Family] filemd5:[FileMD5] filesha1:[FileSHA1] filesha256:[FileSHA256] filesize:[FileSize] ppf:[PPf] score:[Score]]

The keys still case-insensitive!

@kansz
Copy link

kansz commented Jun 2, 2020

UnmarshalKey

This is a workaround for case sensitive

@zoulux
Copy link

zoulux commented Aug 6, 2022

+1

@zsyo
Copy link

zsyo commented Mar 11, 2024

It is already 2024, and I still have not seen the v2 version of Viper. The corresponding issue seems to have been put on hold, which has forced me to abandon the use of Viper in some projects. This is because in some cases, I need to ensure that the configuration keys are not converted to lowercase. For instance, I want to place a map in the configuration, but when it is read back, it has changed its appearance.This is because it might be a name.

config.yaml:
users: {"Bob": 21, "Jeff": 18}

In English, there are also words where the meaning is different when written with different cases, isn’t there? For example, “China” and “china.”

@lyf571321556
Copy link

any update?

@mhkarimi1383
Copy link

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests