diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index 34b627b3c66..c4356ff5857 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -2,6 +2,7 @@ package api import ( "context" + "encoding/json" "errors" "fmt" "path/filepath" @@ -643,10 +644,14 @@ func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]int c := config.GetInstance() if input != nil { + // #5483 - convert JSON numbers to float64 or int64 + input = utils.ConvertMapJSONNumbers(input) c.SetUIConfiguration(input) } if partial != nil { + // #5483 - convert JSON numbers to float64 or int64 + partial = utils.ConvertMapJSONNumbers(partial) // merge partial into existing config existing := c.GetUIConfiguration() utils.MergeMaps(existing, partial) @@ -664,6 +669,14 @@ func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, v c := config.GetInstance() cfg := utils.NestedMap(c.GetUIConfiguration()) + + // #5483 - convert JSON numbers to float64 or int64 + if m, ok := value.(map[string]interface{}); ok { + value = utils.ConvertMapJSONNumbers(m) + } else if n, ok := value.(json.Number); ok { + value = utils.JSONNumberToNumber(n) + } + cfg.Set(key, value) return r.ConfigureUI(ctx, cfg, nil) @@ -671,6 +684,9 @@ func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, v func (r *mutationResolver) ConfigurePlugin(ctx context.Context, pluginID string, input map[string]interface{}) (map[string]interface{}, error) { c := config.GetInstance() + + // #5483 - convert JSON numbers to float64 or int64 + input = utils.ConvertMapJSONNumbers(input) c.SetPluginConfiguration(pluginID, input) if err := c.Write(); err != nil { diff --git a/pkg/utils/json.go b/pkg/utils/json.go new file mode 100644 index 00000000000..ae69180688c --- /dev/null +++ b/pkg/utils/json.go @@ -0,0 +1,16 @@ +package utils + +import ( + "encoding/json" + "strings" +) + +// JSONNumberToNumber converts a JSON number to either a float64 or int64. +func JSONNumberToNumber(n json.Number) interface{} { + if strings.Contains(string(n), ".") { + f, _ := n.Float64() + return f + } + ret, _ := n.Int64() + return ret +} diff --git a/pkg/utils/map.go b/pkg/utils/map.go index 0c555857443..dbef17646b2 100644 --- a/pkg/utils/map.go +++ b/pkg/utils/map.go @@ -1,6 +1,7 @@ package utils import ( + "encoding/json" "strings" ) @@ -79,3 +80,19 @@ func MergeMaps(dest map[string]interface{}, src map[string]interface{}) { dest[k] = v } } + +// ConvertMapJSONNumbers converts all JSON numbers in a map to either float64 or int64. +func ConvertMapJSONNumbers(m map[string]interface{}) (ret map[string]interface{}) { + ret = make(map[string]interface{}) + for k, v := range m { + if n, ok := v.(json.Number); ok { + ret[k] = JSONNumberToNumber(n) + } else if mm, ok := v.(map[string]interface{}); ok { + ret[k] = ConvertMapJSONNumbers(mm) + } else { + ret[k] = v + } + } + + return ret +} diff --git a/pkg/utils/map_test.go b/pkg/utils/map_test.go index 54dfacedd30..142cd639321 100644 --- a/pkg/utils/map_test.go +++ b/pkg/utils/map_test.go @@ -1,8 +1,11 @@ package utils import ( + "encoding/json" "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestNestedMapGet(t *testing.T) { @@ -279,3 +282,55 @@ func TestMergeMaps(t *testing.T) { }) } } + +func TestConvertMapJSONNumbers(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + expected map[string]interface{} + }{ + { + name: "Convert JSON numbers to numbers", + input: map[string]interface{}{ + "int": json.Number("12"), + "float": json.Number("12.34"), + "string": "foo", + }, + expected: map[string]interface{}{ + "int": int64(12), + "float": 12.34, + "string": "foo", + }, + }, + { + name: "Convert JSON numbers to numbers in nested maps", + input: map[string]interface{}{ + "foo": map[string]interface{}{ + "int": json.Number("56"), + "float": json.Number("56.78"), + "nested-string": "bar", + }, + "int": json.Number("12"), + "float": json.Number("12.34"), + "string": "foo", + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "int": int64(56), + "float": 56.78, + "nested-string": "bar", + }, + "int": int64(12), + "float": 12.34, + "string": "foo", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ConvertMapJSONNumbers(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +}