blob: c421a62ed949ecc02a30376516dd8aa94a7b0b4e [file] [log] [blame]
package cty
import (
"bytes"
"encoding/json"
"fmt"
)
// MarshalJSON is an implementation of json.Marshaler that allows Type
// instances to be serialized as JSON.
//
// All standard types can be serialized, but capsule types cannot since there
// is no way to automatically recover the original pointer and capsule types
// compare by equality.
func (t Type) MarshalJSON() ([]byte, error) {
switch impl := t.typeImpl.(type) {
case primitiveType:
switch impl.Kind {
case primitiveTypeBool:
return []byte{'"', 'b', 'o', 'o', 'l', '"'}, nil
case primitiveTypeNumber:
return []byte{'"', 'n', 'u', 'm', 'b', 'e', 'r', '"'}, nil
case primitiveTypeString:
return []byte{'"', 's', 't', 'r', 'i', 'n', 'g', '"'}, nil
default:
panic("unknown primitive type kind")
}
case typeList, typeMap, typeSet:
buf := &bytes.Buffer{}
etyJSON, err := t.ElementType().MarshalJSON()
if err != nil {
return nil, err
}
buf.WriteRune('[')
switch impl.(type) {
case typeList:
buf.WriteString(`"list"`)
case typeMap:
buf.WriteString(`"map"`)
case typeSet:
buf.WriteString(`"set"`)
}
buf.WriteRune(',')
buf.Write(etyJSON)
buf.WriteRune(']')
return buf.Bytes(), nil
case typeObject:
buf := &bytes.Buffer{}
atysJSON, err := json.Marshal(t.AttributeTypes())
if err != nil {
return nil, err
}
buf.WriteString(`["object",`)
buf.Write(atysJSON)
buf.WriteRune(']')
return buf.Bytes(), nil
case typeTuple:
buf := &bytes.Buffer{}
etysJSON, err := json.Marshal(t.TupleElementTypes())
if err != nil {
return nil, err
}
buf.WriteString(`["tuple",`)
buf.Write(etysJSON)
buf.WriteRune(']')
return buf.Bytes(), nil
case pseudoTypeDynamic:
return []byte{'"', 'd', 'y', 'n', 'a', 'm', 'i', 'c', '"'}, nil
case *capsuleType:
return nil, fmt.Errorf("type not allowed: %s", t.FriendlyName())
default:
// should never happen
panic("unknown type implementation")
}
}
// UnmarshalJSON is the opposite of MarshalJSON. See the documentation of
// MarshalJSON for information on the limitations of JSON serialization of
// types.
func (t *Type) UnmarshalJSON(buf []byte) error {
r := bytes.NewReader(buf)
dec := json.NewDecoder(r)
tok, err := dec.Token()
if err != nil {
return err
}
switch v := tok.(type) {
case string:
switch v {
case "bool":
*t = Bool
case "number":
*t = Number
case "string":
*t = String
case "dynamic":
*t = DynamicPseudoType
default:
return fmt.Errorf("invalid primitive type name %q", v)
}
if dec.More() {
return fmt.Errorf("extraneous data after type description")
}
return nil
case json.Delim:
if rune(v) != '[' {
return fmt.Errorf("invalid complex type description")
}
tok, err = dec.Token()
if err != nil {
return err
}
kind, ok := tok.(string)
if !ok {
return fmt.Errorf("invalid complex type kind name")
}
switch kind {
case "list":
var ety Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = List(ety)
case "map":
var ety Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = Map(ety)
case "set":
var ety Type
err = dec.Decode(&ety)
if err != nil {
return err
}
*t = Set(ety)
case "object":
var atys map[string]Type
err = dec.Decode(&atys)
if err != nil {
return err
}
*t = Object(atys)
case "tuple":
var etys []Type
err = dec.Decode(&etys)
if err != nil {
return err
}
*t = Tuple(etys)
default:
return fmt.Errorf("invalid complex type kind name")
}
tok, err = dec.Token()
if err != nil {
return err
}
if delim, ok := tok.(json.Delim); !ok || rune(delim) != ']' || dec.More() {
return fmt.Errorf("unexpected extra data in type description")
}
return nil
default:
return fmt.Errorf("invalid type description")
}
}