in lib/json/json.go [84:227]
func encode(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x starlark.Value
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &x); err != nil {
return nil, err
}
buf := new(bytes.Buffer)
var quoteSpace [128]byte
quote := func(s string) {
// Non-trivial escaping is handled by Go's encoding/json.
if isPrintableASCII(s) {
buf.Write(strconv.AppendQuote(quoteSpace[:0], s))
} else {
// TODO(adonovan): opt: RFC 8259 mandates UTF-8 for JSON.
// Can we avoid this call?
data, _ := json.Marshal(s)
buf.Write(data)
}
}
path := make([]unsafe.Pointer, 0, 8)
var emit func(x starlark.Value) error
emit = func(x starlark.Value) error {
// It is only necessary to push/pop the item when it might contain
// itself (i.e. the last three switch cases), but omitting it in the other
// cases did not show significant improvement on the benchmarks.
if ptr := pointer(x); ptr != nil {
if pathContains(path, ptr) {
return fmt.Errorf("cycle in JSON structure")
}
path = append(path, ptr)
defer func() { path = path[0 : len(path)-1] }()
}
switch x := x.(type) {
case json.Marshaler:
// Application-defined starlark.Value types
// may define their own JSON encoding.
data, err := x.MarshalJSON()
if err != nil {
return err
}
buf.Write(data)
case starlark.NoneType:
buf.WriteString("null")
case starlark.Bool:
if x {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
case starlark.Int:
fmt.Fprint(buf, x)
case starlark.Float:
if !isFinite(float64(x)) {
return fmt.Errorf("cannot encode non-finite float %v", x)
}
fmt.Fprintf(buf, "%g", x) // always contains a decimal point
case starlark.String:
quote(string(x))
case starlark.IterableMapping:
// e.g. dict (must have string keys)
buf.WriteByte('{')
items := x.Items()
for _, item := range items {
if _, ok := item[0].(starlark.String); !ok {
return fmt.Errorf("%s has %s key, want string", x.Type(), item[0].Type())
}
}
sort.Slice(items, func(i, j int) bool {
return items[i][0].(starlark.String) < items[j][0].(starlark.String)
})
for i, item := range items {
if i > 0 {
buf.WriteByte(',')
}
k, _ := starlark.AsString(item[0])
quote(k)
buf.WriteByte(':')
if err := emit(item[1]); err != nil {
return fmt.Errorf("in %s key %s: %v", x.Type(), item[0], err)
}
}
buf.WriteByte('}')
case starlark.Iterable:
// e.g. tuple, list
buf.WriteByte('[')
iter := x.Iterate()
defer iter.Done()
var elem starlark.Value
for i := 0; iter.Next(&elem); i++ {
if i > 0 {
buf.WriteByte(',')
}
if err := emit(elem); err != nil {
return fmt.Errorf("at %s index %d: %v", x.Type(), i, err)
}
}
buf.WriteByte(']')
case starlark.HasAttrs:
// e.g. struct
buf.WriteByte('{')
var names []string
names = append(names, x.AttrNames()...)
sort.Strings(names)
for i, name := range names {
v, err := x.Attr(name)
if err != nil || v == nil {
log.Fatalf("internal error: dir(%s) includes %q but value has no .%s field", x.Type(), name, name)
}
if i > 0 {
buf.WriteByte(',')
}
quote(name)
buf.WriteByte(':')
if err := emit(v); err != nil {
return fmt.Errorf("in field .%s: %v", name, err)
}
}
buf.WriteByte('}')
default:
return fmt.Errorf("cannot encode %s as JSON", x.Type())
}
return nil
}
if err := emit(x); err != nil {
return nil, fmt.Errorf("%s: %v", b.Name(), err)
}
return starlark.String(buf.String()), nil
}