diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index b934a5b83..2c5e660a6 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -824,8 +824,7 @@ func interpolationFuncJoin() ast.Function { } // interpolationFuncJSONEncode implements the "jsonencode" function that encodes -// a string, list, or map as its JSON representation. For now, values in the -// list or map may only be strings. +// a string, list, or map as its JSON representation. func interpolationFuncJSONEncode() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeAny}, @@ -838,28 +837,36 @@ func interpolationFuncJSONEncode() ast.Function { toEncode = typedArg case []ast.Variable: - // We preallocate the list here. Note that it's important that in - // the length 0 case, we have an empty list rather than nil, as - // they encode differently. - // XXX It would be nice to support arbitrarily nested data here. Is - // there an inverse of hil.InterfaceToVariable? strings := make([]string, len(typedArg)) for i, v := range typedArg { if v.Type != ast.TypeString { - return "", fmt.Errorf("list elements must be strings") + variable, _ := hil.InterfaceToVariable(typedArg) + toEncode, _ = hil.VariableToInterface(variable) + + jEnc, err := json.Marshal(toEncode) + if err != nil { + return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) + } + return string(jEnc), nil + } strings[i] = v.Value.(string) } toEncode = strings case map[string]ast.Variable: - // XXX It would be nice to support arbitrarily nested data here. Is - // there an inverse of hil.InterfaceToVariable? stringMap := make(map[string]string) for k, v := range typedArg { if v.Type != ast.TypeString { - return "", fmt.Errorf("map values must be strings") + variable, _ := hil.InterfaceToVariable(typedArg) + toEncode, _ = hil.VariableToInterface(variable) + + jEnc, err := json.Marshal(toEncode) + if err != nil { + return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) + } + return string(jEnc), nil } stringMap[k] = v.Value.(string) } diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 9878c0fef..8ed0734a6 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -1406,8 +1406,6 @@ func TestInterpolateFuncJSONEncode(t *testing.T) { Type: ast.TypeString, }, "list": interfaceToVariableSwallowError([]string{"foo", "bar\tbaz"}), - // XXX can't use InterfaceToVariable as it converts empty slice into empty - // map. "emptylist": ast.Variable{ Value: []ast.Variable{}, Type: ast.TypeList, @@ -1416,9 +1414,7 @@ func TestInterpolateFuncJSONEncode(t *testing.T) { "foo": "bar", "ba \n z": "q\\x", }), - "emptymap": interfaceToVariableSwallowError(map[string]string{}), - - // Not yet supported (but it would be nice) + "emptymap": interfaceToVariableSwallowError(map[string]string{}), "nestedlist": interfaceToVariableSwallowError([][]string{{"foo"}}), "nestedmap": interfaceToVariableSwallowError(map[string][]string{"foo": {"bar"}}), }, @@ -1470,13 +1466,13 @@ func TestInterpolateFuncJSONEncode(t *testing.T) { }, { `${jsonencode(nestedlist)}`, - nil, - true, + `[["foo"]]`, + false, }, { `${jsonencode(nestedmap)}`, - nil, - true, + `{"foo":["bar"]}`, + false, }, }, }) diff --git a/website/docs/configuration/interpolation.html.md b/website/docs/configuration/interpolation.html.md index fd1598894..aea83516c 100644 --- a/website/docs/configuration/interpolation.html.md +++ b/website/docs/configuration/interpolation.html.md @@ -262,10 +262,9 @@ The supported built-in functions are: * `join(",", aws_instance.foo.*.id)` * `join(",", var.ami_list)` - * `jsonencode(item)` - Returns a JSON-encoded representation of the given - item, which may be a string, list of strings, or map from string to string. - Note that if the item is a string, the return value includes the double - quotes. + * `jsonencode(value)` - Returns a JSON-encoded representation of the given + value, which can contain arbitrarily-nested lists and maps. Note that if + the value is a string then its value will be placed in quotes. * `keys(map)` - Returns a lexically sorted list of the map keys.