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

How to convert cty.Value of unknown type into interface{} #158

Closed
manterfield opened this issue Mar 23, 2023 · 2 comments
Closed

How to convert cty.Value of unknown type into interface{} #158

manterfield opened this issue Mar 23, 2023 · 2 comments

Comments

@manterfield
Copy link

manterfield commented Mar 23, 2023

I'm probably just missing something obvious here, and if so apologies for the noise.

I'm attempting to take user input from HCL that is composed of data with types that are unknown until runtime. The example HCL could look something like this:

myblock {
  user_provided_attrs = {
    foo = "foostring"
    bar = true
    buzz = vars.some_complex_var
  }
}

My first attempt was to decode this into cty Values and then transform using gocty.FromCtyValue(val, &decodedVal) where decoded val is map[string]interface{}. Of course, that doesn't work as FromCtyValue uses reflection on the type and maps to specific concrete types. interface{} doesn't match, so I get error: "incorrect type" which is the default error case.

I can see I could put something together using recursion myself, and if all else fails that's what I'll do. The only thing that has stopped me is the assumption that I must be missing a built in way to handle this case. Especially since the package docs have quite a bit of material on converting between unknown types etc.

@ljluestc
Copy link

package main

import (
	"fmt"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/json"
)

func convertToInterface(val cty.Value) interface{} {
	switch val.Type() {
	case cty.String:
		return val.AsString()
	case cty.Bool:
		return val.True()
	case cty.Number:
		return val.AsBigFloat().String()
	case cty.Object:
		obj := make(map[string]interface{})
		val.ForEach(func(key cty.Value, value cty.Value) bool {
			obj[key.AsString()] = convertToInterface(value)
			return true
		})
		return obj
	case cty.List:
		list := make([]interface{}, 0)
		val.ForEachElement(func(index int, value cty.Value) bool {
			list = append(list, convertToInterface(value))
			return true
		})
		return list
	default:
		return nil
	}
}

func main() {
	hcl := `{
		"foo": "foostring",
		"bar": true,
		"buzz": [1, 2, 3]
	}`

	val, err := json.Unmarshal([]byte(hcl), cty.DynamicPseudoType)
	if err != nil {
		fmt.Println(err)
		return
	}

	result := convertToInterface(val)

	fmt.Printf("%+v\n", result)
}

@manterfield
Copy link
Author

@ljluestc Lovely example, thank you. This is the recursive version I had in mind, it's fairly neat so probably not a big deal that it's not built in.

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

2 participants