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

avoid type loss with JSON unmarshal/marshal #64

Merged
merged 5 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ func (ko *Koanf) Int64s(path string) []int64 {

var out []int64
switch v := o.(type) {
case []int64:
return v
case []int:
out = make([]int64, 0, len(v))
for _, vi := range v {
i, err := toInt64(vi)

// On error, return as it's not a valid
// int slice.
if err != nil {
return []int64{}
}
out = append(out, i)
}
return out
case []interface{}:
out = make([]int64, 0, len(v))
for _, vi := range v {
Expand Down Expand Up @@ -128,16 +143,37 @@ func (ko *Koanf) MustInt(path string) int {
// empty []int slice if the path does not exist or if the value
// is not a valid int slice.
func (ko *Koanf) Ints(path string) []int {
ints := ko.Int64s(path)
if len(ints) == 0 {
o := ko.Get(path)
if o == nil {
return []int{}
}

out := make([]int, len(ints))
for i, v := range ints {
out[i] = int(v)
var out []int
switch v := o.(type) {
case []int:
return v
case []int64:
out = make([]int, 0, len(v))
for _, vi := range v {
out = append(out, int(vi))
}
return out
case []interface{}:
knadh marked this conversation as resolved.
Show resolved Hide resolved
out = make([]int, 0, len(v))
for _, vi := range v {
i, err := toInt64(vi)

// On error, return as it's not a valid
// int slice.
if err != nil {
return []int{}
}
out = append(out, int(i))
}
return out
}
return out

return []int{}
}

// MustInts returns the []int slice value of a given key path or panics
Expand Down Expand Up @@ -205,6 +241,8 @@ func (ko *Koanf) Float64s(path string) []float64 {

var out []float64
switch v := o.(type) {
case []float64:
return v
case []interface{}:
out = make([]float64, 0, len(v))
for _, vi := range v {
Expand All @@ -215,7 +253,7 @@ func (ko *Koanf) Float64s(path string) []float64 {
if err != nil {
return []float64{}
}
out = append(out, float64(i))
out = append(out, i)
}
return out
}
Expand Down Expand Up @@ -379,6 +417,7 @@ func (ko *Koanf) Strings(path string) []string {
copy(out[:], v[:])
return out
}

return []string{}
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/vault/api v1.0.4
github.com/joho/godotenv v1.3.0
github.com/mitchellh/copystructure v1.1.1
github.com/mitchellh/mapstructure v1.2.2
github.com/pelletier/go-toml v1.7.0
github.com/rhnvrm/simples3 v0.6.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
Expand All @@ -67,6 +69,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
Expand Down
11 changes: 5 additions & 6 deletions koanf.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package koanf

import (
"bytes"
"encoding/json"
"fmt"
"github.com/mitchellh/copystructure"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -300,11 +300,10 @@ func (ko *Koanf) Get(path string) interface{} {
return maps.Copy(v)
}

// Inefficient, but marshal and unmarshal to create a copy
// of reference types to not expose internal references to slices and maps.
var out interface{}
b, _ := json.Marshal(res)
json.Unmarshal(b, &out)
out, _ := copystructure.Copy(&res)
if ptrOut, ok := out.(*interface{}); ok {
return *ptrOut
}
return out
}

Expand Down
100 changes: 99 additions & 1 deletion koanf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,58 @@ func TestLoadFileAllKeys(t *testing.T) {
}
}

func TestLoadMergeYamlJson(t *testing.T) {
var (
assert = assert.New(t)
k = koanf.New(delim)
)

assert.NoError(k.Load(file.Provider(mockYAML), yaml.Parser()),
"error loading file")
// loading json after yaml causes the intbools to be loaded as []float64
assert.NoError(k.Load(file.Provider(mockJSON), yaml.Parser()),
"error loading file")

// checking that there is no issues with expecting it to be an []int64
v := k.Int64s("intbools")
assert.Len(v, 3)

defer func() {
if err := recover(); err != nil {
assert.Failf("panic", "received panic: %v", err)
}
}()

v2 := k.MustInt64s("intbools")
assert.Len(v2, 3)
}

func TestLoadMergeJsonYaml(t *testing.T) {
var (
assert = assert.New(t)
k = koanf.New(delim)
)

assert.NoError(k.Load(file.Provider(mockJSON), yaml.Parser()),
"error loading file")
// loading yaml after json causes the intbools to be loaded as []int after json loaded it with []float64
assert.NoError(k.Load(file.Provider(mockYAML), yaml.Parser()),
"error loading file")

// checking that there is no issues with expecting it to be an []float64
v := k.Float64s("intbools")
assert.Len(v, 3)

defer func() {
if err := recover(); err != nil {
assert.Failf("panic", "received panic: %v", err)
}
}()

v2 := k.MustFloat64s("intbools")
assert.Len(v2, 3)
}

func TestWatchFile(t *testing.T) {
var (
assert = assert.New(t)
Expand Down Expand Up @@ -614,13 +666,59 @@ func TestMerge(t *testing.T) {
assert.Equal(cut1.All(), k2.All(), "conf map mismatch")
}

func TestMergeAt(t *testing.T) {
func TestRaw_YamlTypes(t *testing.T) {
var (
assert = assert.New(t)
k = koanf.New(delim)
)

assert.Nil(k.Load(file.Provider(mockYAML), yaml.Parser()),
"error loading file")
raw := k.Raw()

i, ok := raw["intbools"]
assert.True(ok, "ints key does not exist in the map")

arr, ok := i.([]interface{})
assert.True(ok, "arr slice is not array of integers")

for _, integer := range arr {
if _, ok := integer.(int); !ok {
assert.Failf("failure", "%v not an integer but %T", integer, integer)
}
}
}

func TestRaw_JsonTypes(t *testing.T) {
var (
assert = assert.New(t)
k = koanf.New(delim)
)

assert.Nil(k.Load(file.Provider(mockJSON), json.Parser()),
"error loading file")
raw := k.Raw()

i, ok := raw["intbools"]
assert.True(ok, "ints key does not exist in the map")

arr, ok := i.([]interface{})
assert.True(ok, "arr slice is not array of integers")

for _, integer := range arr {
if _, ok := integer.(float64); !ok {
assert.Failf("failure", "%v not an integer but %T", integer, integer)
}
}
}

func TestMergeAt(t *testing.T) {
var (
assert = assert.New(t)
k = koanf.New(delim)
)
assert.Nil(k.Load(file.Provider(mockYAML), yaml.Parser()),
"error loading file")

// Get expected koanf, and root data
var (
Expand Down
15 changes: 7 additions & 8 deletions maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
package maps

import (
"encoding/json"
"fmt"
"github.com/mitchellh/copystructure"
"strings"
)

Expand Down Expand Up @@ -182,18 +182,17 @@ func Search(mp map[string]interface{}, path []string) interface{} {
return nil
}

// Copy returns a copy of a conf map by doing a JSON marshal+unmarshal
// pass. Inefficient, but creates a true deep copy. There is a side
// effect, that is, all numeric types change to float64.
// Copy returns a deep copy of a conf map.
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
func Copy(mp map[string]interface{}) map[string]interface{} {
rhnvrm marked this conversation as resolved.
Show resolved Hide resolved
var out map[string]interface{}
b, _ := json.Marshal(mp)
json.Unmarshal(b, &out)
return out
out, _ := copystructure.Copy(&mp)
if res, ok := out.(*map[string]interface{}); ok {
return *res
}
return map[string]interface{}{}
}

// IntfaceKeysToStrings recursively converts map[interface{}]interface{} to
Expand Down