171 lines
3.6 KiB
Go
171 lines
3.6 KiB
Go
|
package json
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/hashicorp/go-cty/cty"
|
||
|
)
|
||
|
|
||
|
// ImpliedType returns the cty Type implied by the structure of the given
|
||
|
// JSON-compliant buffer. This function implements the default type mapping
|
||
|
// behavior used when decoding arbitrary JSON without explicit cty Type
|
||
|
// information.
|
||
|
//
|
||
|
// The rules are as follows:
|
||
|
//
|
||
|
// JSON strings, numbers and bools map to their equivalent primitive type in
|
||
|
// cty.
|
||
|
//
|
||
|
// JSON objects map to cty object types, with the attributes defined by the
|
||
|
// object keys and the types of their values.
|
||
|
//
|
||
|
// JSON arrays map to cty tuple types, with the elements defined by the
|
||
|
// types of the array members.
|
||
|
//
|
||
|
// Any nulls are typed as DynamicPseudoType, so callers of this function
|
||
|
// must be prepared to deal with this. Callers that do not wish to deal with
|
||
|
// dynamic typing should not use this function and should instead describe
|
||
|
// their required types explicitly with a cty.Type instance when decoding.
|
||
|
//
|
||
|
// Any JSON syntax errors will be returned as an error, and the type will
|
||
|
// be the invalid value cty.NilType.
|
||
|
func ImpliedType(buf []byte) (cty.Type, error) {
|
||
|
r := bytes.NewReader(buf)
|
||
|
dec := json.NewDecoder(r)
|
||
|
dec.UseNumber()
|
||
|
|
||
|
ty, err := impliedType(dec)
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
if dec.More() {
|
||
|
return cty.NilType, fmt.Errorf("extraneous data after JSON object")
|
||
|
}
|
||
|
|
||
|
return ty, nil
|
||
|
}
|
||
|
|
||
|
func impliedType(dec *json.Decoder) (cty.Type, error) {
|
||
|
tok, err := dec.Token()
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
return impliedTypeForTok(tok, dec)
|
||
|
}
|
||
|
|
||
|
func impliedTypeForTok(tok json.Token, dec *json.Decoder) (cty.Type, error) {
|
||
|
if tok == nil {
|
||
|
return cty.DynamicPseudoType, nil
|
||
|
}
|
||
|
|
||
|
switch ttok := tok.(type) {
|
||
|
case bool:
|
||
|
return cty.Bool, nil
|
||
|
|
||
|
case json.Number:
|
||
|
return cty.Number, nil
|
||
|
|
||
|
case string:
|
||
|
return cty.String, nil
|
||
|
|
||
|
case json.Delim:
|
||
|
|
||
|
switch rune(ttok) {
|
||
|
case '{':
|
||
|
return impliedObjectType(dec)
|
||
|
case '[':
|
||
|
return impliedTupleType(dec)
|
||
|
default:
|
||
|
return cty.NilType, fmt.Errorf("unexpected token %q", ttok)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return cty.NilType, fmt.Errorf("unsupported JSON token %#v", tok)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func impliedObjectType(dec *json.Decoder) (cty.Type, error) {
|
||
|
// By the time we get in here, we've already consumed the { delimiter
|
||
|
// and so our next token should be the first object key.
|
||
|
|
||
|
var atys map[string]cty.Type
|
||
|
|
||
|
for {
|
||
|
// Read the object key first
|
||
|
tok, err := dec.Token()
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
if ttok, ok := tok.(json.Delim); ok {
|
||
|
if rune(ttok) != '}' {
|
||
|
return cty.NilType, fmt.Errorf("unexpected delimiter %q", ttok)
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
|
||
|
key, ok := tok.(string)
|
||
|
if !ok {
|
||
|
return cty.NilType, fmt.Errorf("expected string but found %T", tok)
|
||
|
}
|
||
|
|
||
|
// Now read the value
|
||
|
tok, err = dec.Token()
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
aty, err := impliedTypeForTok(tok, dec)
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
if atys == nil {
|
||
|
atys = make(map[string]cty.Type)
|
||
|
}
|
||
|
atys[key] = aty
|
||
|
}
|
||
|
|
||
|
if len(atys) == 0 {
|
||
|
return cty.EmptyObject, nil
|
||
|
}
|
||
|
|
||
|
return cty.Object(atys), nil
|
||
|
}
|
||
|
|
||
|
func impliedTupleType(dec *json.Decoder) (cty.Type, error) {
|
||
|
// By the time we get in here, we've already consumed the [ delimiter
|
||
|
// and so our next token should be the first value.
|
||
|
|
||
|
var etys []cty.Type
|
||
|
|
||
|
for {
|
||
|
tok, err := dec.Token()
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
|
||
|
if ttok, ok := tok.(json.Delim); ok {
|
||
|
if rune(ttok) == ']' {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ety, err := impliedTypeForTok(tok, dec)
|
||
|
if err != nil {
|
||
|
return cty.NilType, err
|
||
|
}
|
||
|
etys = append(etys, ety)
|
||
|
}
|
||
|
|
||
|
if len(etys) == 0 {
|
||
|
return cty.EmptyTuple, nil
|
||
|
}
|
||
|
|
||
|
return cty.Tuple(etys), nil
|
||
|
}
|