| package json |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "sort" |
| |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error { |
| // If we're going to decode as DynamicPseudoType then we need to save |
| // dynamic type information to recover the real type. |
| if t == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType { |
| return marshalDynamic(val, path, b) |
| } |
| |
| if val.IsNull() { |
| b.WriteString("null") |
| return nil |
| } |
| |
| if !val.IsKnown() { |
| return path.NewErrorf("value is not known") |
| } |
| |
| // The caller should've guaranteed that the given val is conformant with |
| // the given type t, so we'll proceed under that assumption here. |
| |
| switch { |
| case t.IsPrimitiveType(): |
| switch t { |
| case cty.String: |
| json, err := json.Marshal(val.AsString()) |
| if err != nil { |
| return path.NewErrorf("failed to serialize value: %s", err) |
| } |
| b.Write(json) |
| return nil |
| case cty.Number: |
| if val.RawEquals(cty.PositiveInfinity) || val.RawEquals(cty.NegativeInfinity) { |
| return path.NewErrorf("cannot serialize infinity as JSON") |
| } |
| b.WriteString(val.AsBigFloat().Text('f', -1)) |
| return nil |
| case cty.Bool: |
| if val.True() { |
| b.WriteString("true") |
| } else { |
| b.WriteString("false") |
| } |
| return nil |
| default: |
| panic("unsupported primitive type") |
| } |
| case t.IsListType(), t.IsSetType(): |
| b.WriteRune('[') |
| first := true |
| ety := t.ElementType() |
| it := val.ElementIterator() |
| path := append(path, nil) // local override of 'path' with extra element |
| for it.Next() { |
| if !first { |
| b.WriteRune(',') |
| } |
| ek, ev := it.Element() |
| path[len(path)-1] = cty.IndexStep{ |
| Key: ek, |
| } |
| err := marshal(ev, ety, path, b) |
| if err != nil { |
| return err |
| } |
| first = false |
| } |
| b.WriteRune(']') |
| return nil |
| case t.IsMapType(): |
| b.WriteRune('{') |
| first := true |
| ety := t.ElementType() |
| it := val.ElementIterator() |
| path := append(path, nil) // local override of 'path' with extra element |
| for it.Next() { |
| if !first { |
| b.WriteRune(',') |
| } |
| ek, ev := it.Element() |
| path[len(path)-1] = cty.IndexStep{ |
| Key: ek, |
| } |
| var err error |
| err = marshal(ek, ek.Type(), path, b) |
| if err != nil { |
| return err |
| } |
| b.WriteRune(':') |
| err = marshal(ev, ety, path, b) |
| if err != nil { |
| return err |
| } |
| first = false |
| } |
| b.WriteRune('}') |
| return nil |
| case t.IsTupleType(): |
| b.WriteRune('[') |
| etys := t.TupleElementTypes() |
| it := val.ElementIterator() |
| path := append(path, nil) // local override of 'path' with extra element |
| i := 0 |
| for it.Next() { |
| if i > 0 { |
| b.WriteRune(',') |
| } |
| ety := etys[i] |
| ek, ev := it.Element() |
| path[len(path)-1] = cty.IndexStep{ |
| Key: ek, |
| } |
| err := marshal(ev, ety, path, b) |
| if err != nil { |
| return err |
| } |
| i++ |
| } |
| b.WriteRune(']') |
| return nil |
| case t.IsObjectType(): |
| b.WriteRune('{') |
| atys := t.AttributeTypes() |
| path := append(path, nil) // local override of 'path' with extra element |
| |
| names := make([]string, 0, len(atys)) |
| for k := range atys { |
| names = append(names, k) |
| } |
| sort.Strings(names) |
| |
| for i, k := range names { |
| aty := atys[k] |
| if i > 0 { |
| b.WriteRune(',') |
| } |
| av := val.GetAttr(k) |
| path[len(path)-1] = cty.GetAttrStep{ |
| Name: k, |
| } |
| var err error |
| err = marshal(cty.StringVal(k), cty.String, path, b) |
| if err != nil { |
| return err |
| } |
| b.WriteRune(':') |
| err = marshal(av, aty, path, b) |
| if err != nil { |
| return err |
| } |
| } |
| b.WriteRune('}') |
| return nil |
| case t.IsCapsuleType(): |
| rawVal := val.EncapsulatedValue() |
| jsonVal, err := json.Marshal(rawVal) |
| if err != nil { |
| return path.NewError(err) |
| } |
| b.Write(jsonVal) |
| return nil |
| default: |
| // should never happen |
| return path.NewErrorf("cannot JSON-serialize %s", t.FriendlyName()) |
| } |
| } |
| |
| // marshalDynamic adds an extra wrapping object containing dynamic type |
| // information for the given value. |
| func marshalDynamic(val cty.Value, path cty.Path, b *bytes.Buffer) error { |
| typeJSON, err := MarshalType(val.Type()) |
| if err != nil { |
| return path.NewErrorf("failed to serialize type: %s", err) |
| } |
| b.WriteString(`{"value":`) |
| marshal(val, val.Type(), path, b) |
| b.WriteString(`,"type":`) |
| b.Write(typeJSON) |
| b.WriteRune('}') |
| return nil |
| } |