// A modified version of Go's JSON implementation. // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package json import ( "encoding/base64" "errors" "math" "strconv" "github.com/d5/tengo/v2" ) // Encode returns the JSON encoding of the object. func Encode(o tengo.Object) ([]byte, error) { var b []byte switch o := o.(type) { case *tengo.Array: b = append(b, '[') len1 := len(o.Value) - 1 for idx, elem := range o.Value { eb, err := Encode(elem) if err != nil { return nil, err } b = append(b, eb...) if idx < len1 { b = append(b, ',') } } b = append(b, ']') case *tengo.ImmutableArray: b = append(b, '[') len1 := len(o.Value) - 1 for idx, elem := range o.Value { eb, err := Encode(elem) if err != nil { return nil, err } b = append(b, eb...) if idx < len1 { b = append(b, ',') } } b = append(b, ']') case *tengo.Map: b = append(b, '{') len1 := len(o.Value) - 1 idx := 0 for key, value := range o.Value { b = strconv.AppendQuote(b, key) b = append(b, ':') eb, err := Encode(value) if err != nil { return nil, err } b = append(b, eb...) if idx < len1 { b = append(b, ',') } idx++ } b = append(b, '}') case *tengo.ImmutableMap: b = append(b, '{') len1 := len(o.Value) - 1 idx := 0 for key, value := range o.Value { b = strconv.AppendQuote(b, key) b = append(b, ':') eb, err := Encode(value) if err != nil { return nil, err } b = append(b, eb...) if idx < len1 { b = append(b, ',') } idx++ } b = append(b, '}') case *tengo.Bool: if o.IsFalsy() { b = strconv.AppendBool(b, false) } else { b = strconv.AppendBool(b, true) } case *tengo.Bytes: b = append(b, '"') encodedLen := base64.StdEncoding.EncodedLen(len(o.Value)) dst := make([]byte, encodedLen) base64.StdEncoding.Encode(dst, o.Value) b = append(b, dst...) b = append(b, '"') case *tengo.Char: b = strconv.AppendInt(b, int64(o.Value), 10) case *tengo.Float: var y []byte f := o.Value if math.IsInf(f, 0) || math.IsNaN(f) { return nil, errors.New("unsupported float value") } // Convert as if by ES6 number to string conversion. // This matches most other JSON generators. abs := math.Abs(f) fmt := byte('f') if abs != 0 { if abs < 1e-6 || abs >= 1e21 { fmt = 'e' } } y = strconv.AppendFloat(y, f, fmt, -1, 64) if fmt == 'e' { // clean up e-09 to e-9 n := len(y) if n >= 4 && y[n-4] == 'e' && y[n-3] == '-' && y[n-2] == '0' { y[n-2] = y[n-1] y = y[:n-1] } } b = append(b, y...) case *tengo.Int: b = strconv.AppendInt(b, o.Value, 10) case *tengo.String: b = strconv.AppendQuote(b, o.Value) case *tengo.Time: y, err := o.Value.MarshalJSON() if err != nil { return nil, err } b = append(b, y...) case *tengo.Undefined: b = append(b, "null"...) default: // unknown type: ignore } return b, nil }