diff options
Diffstat (limited to 'vendor/github.com/pquerna/ffjson/inception/encoder.go')
-rw-r--r-- | vendor/github.com/pquerna/ffjson/inception/encoder.go | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/vendor/github.com/pquerna/ffjson/inception/encoder.go b/vendor/github.com/pquerna/ffjson/inception/encoder.go new file mode 100644 index 000000000..3e37a2814 --- /dev/null +++ b/vendor/github.com/pquerna/ffjson/inception/encoder.go @@ -0,0 +1,544 @@ +/** + * Copyright 2014 Paul Querna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ffjsoninception + +import ( + "fmt" + "reflect" + + "github.com/pquerna/ffjson/shared" +) + +func typeInInception(ic *Inception, typ reflect.Type, f shared.Feature) bool { + for _, v := range ic.objs { + if v.Typ == typ { + return v.Options.HasFeature(f) + } + if typ.Kind() == reflect.Ptr { + if v.Typ == typ.Elem() { + return v.Options.HasFeature(f) + } + } + } + + return false +} + +func getOmitEmpty(ic *Inception, sf *StructField) string { + ptname := "j." + sf.Name + if sf.Pointer { + ptname = "*" + ptname + return "if true {\n" + } + switch sf.Typ.Kind() { + + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return "if len(" + ptname + ") != 0 {" + "\n" + + case reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr, + reflect.Float32, + reflect.Float64: + return "if " + ptname + " != 0 {" + "\n" + + case reflect.Bool: + return "if " + ptname + " != false {" + "\n" + + case reflect.Interface, reflect.Ptr: + return "if " + ptname + " != nil {" + "\n" + + default: + // TODO(pquerna): fix types + return "if true {" + "\n" + } +} + +func getMapValue(ic *Inception, name string, typ reflect.Type, ptr bool, forceString bool) string { + var out = "" + + if typ.Key().Kind() != reflect.String { + out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind()) + out += ic.q.Flush() + out += "err = buf.Encode(" + name + ")" + "\n" + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + return out + } + + var elemKind reflect.Kind + elemKind = typ.Elem().Kind() + + switch elemKind { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, + reflect.Float64, + reflect.Bool: + + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + + out += "if " + name + " == nil {" + "\n" + ic.q.Write("null") + out += ic.q.GetQueued() + ic.q.DeleteLast() + out += "} else {" + "\n" + out += ic.q.WriteFlush("{ ") + out += " for key, value := range " + name + " {" + "\n" + out += " fflib.WriteJsonString(buf, key)" + "\n" + out += " buf.WriteString(`:`)" + "\n" + out += getGetInnerValue(ic, "value", typ.Elem(), false, forceString) + out += " buf.WriteByte(',')" + "\n" + out += " }" + "\n" + out += "buf.Rewind(1)" + "\n" + out += ic.q.WriteFlush("}") + out += "}" + "\n" + + default: + out += ic.q.Flush() + out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind()) + out += "err = buf.Encode(" + name + ")" + "\n" + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + } + return out +} + +func getGetInnerValue(ic *Inception, name string, typ reflect.Type, ptr bool, forceString bool) string { + var out = "" + + // Flush if not bool or maps + if typ.Kind() != reflect.Bool && typ.Kind() != reflect.Map && typ.Kind() != reflect.Struct { + out += ic.q.Flush() + } + + if typ.Implements(marshalerFasterType) || + reflect.PtrTo(typ).Implements(marshalerFasterType) || + typeInInception(ic, typ, shared.MustEncoder) || + typ.Implements(marshalerType) || + reflect.PtrTo(typ).Implements(marshalerType) { + + out += ic.q.Flush() + out += tplStr(encodeTpl["handleMarshaler"], handleMarshaler{ + IC: ic, + Name: name, + Typ: typ, + Ptr: reflect.Ptr, + MarshalJSONBuf: typ.Implements(marshalerFasterType) || reflect.PtrTo(typ).Implements(marshalerFasterType) || typeInInception(ic, typ, shared.MustEncoder), + Marshaler: typ.Implements(marshalerType) || reflect.PtrTo(typ).Implements(marshalerType), + }) + return out + } + + ptname := name + if ptr { + ptname = "*" + name + } + + switch typ.Kind() { + case reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64: + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + out += "fflib.FormatBits2(buf, uint64(" + ptname + "), 10, " + ptname + " < 0)" + "\n" + case reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr: + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + out += "fflib.FormatBits2(buf, uint64(" + ptname + "), 10, false)" + "\n" + case reflect.Float32: + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + out += "fflib.AppendFloat(buf, float64(" + ptname + "), 'g', -1, 32)" + "\n" + case reflect.Float64: + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + out += "fflib.AppendFloat(buf, float64(" + ptname + "), 'g', -1, 64)" + "\n" + case reflect.Array, + reflect.Slice: + + // Arrays cannot be nil + if typ.Kind() != reflect.Array { + out += "if " + name + "!= nil {" + "\n" + } + // Array and slice values encode as JSON arrays, except that + // []byte encodes as a base64-encoded string, and a nil slice + // encodes as the null JSON object. + if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { + ic.OutputImports[`"encoding/base64"`] = true + + out += "buf.WriteString(`\"`)" + "\n" + out += `{` + "\n" + out += `enc := base64.NewEncoder(base64.StdEncoding, buf)` + "\n" + if typ.Elem().Name() != "byte" { + ic.OutputImports[`"reflect"`] = true + out += `enc.Write(reflect.Indirect(reflect.ValueOf(` + ptname + `)).Bytes())` + "\n" + + } else { + out += `enc.Write(` + ptname + `)` + "\n" + } + out += `enc.Close()` + "\n" + out += `}` + "\n" + out += "buf.WriteString(`\"`)" + "\n" + } else { + out += "buf.WriteString(`[`)" + "\n" + out += "for i, v := range " + ptname + "{" + "\n" + out += "if i != 0 {" + "\n" + out += "buf.WriteString(`,`)" + "\n" + out += "}" + "\n" + out += getGetInnerValue(ic, "v", typ.Elem(), false, false) + out += "}" + "\n" + out += "buf.WriteString(`]`)" + "\n" + } + if typ.Kind() != reflect.Array { + out += "} else {" + "\n" + out += "buf.WriteString(`null`)" + "\n" + out += "}" + "\n" + } + case reflect.String: + // Is it a json.Number? + if typ.PkgPath() == "encoding/json" && typ.Name() == "Number" { + // Fall back to json package to rely on the valid number check. + // See: https://github.com/golang/go/blob/92cd6e3af9f423ab4d8ac78f24e7fd81c31a8ce6/src/encoding/json/encode.go#L550 + out += fmt.Sprintf("/* json.Number */\n") + out += "err = buf.Encode(" + name + ")" + "\n" + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + } else { + ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true + if forceString { + // Forcestring on strings does double-escaping of the entire value. + // We create a temporary buffer, encode to that an re-encode it. + out += "{" + "\n" + out += "tmpbuf := fflib.Buffer{}" + "\n" + out += "tmpbuf.Grow(len(" + ptname + ") + 16)" + "\n" + out += "fflib.WriteJsonString(&tmpbuf, string(" + ptname + "))" + "\n" + out += "fflib.WriteJsonString(buf, string( tmpbuf.Bytes() " + `))` + "\n" + out += "}" + "\n" + } else { + out += "fflib.WriteJsonString(buf, string(" + ptname + "))" + "\n" + } + } + case reflect.Ptr: + out += "if " + name + "!= nil {" + "\n" + switch typ.Elem().Kind() { + case reflect.Struct: + out += getGetInnerValue(ic, name, typ.Elem(), false, false) + default: + out += getGetInnerValue(ic, "*"+name, typ.Elem(), false, false) + } + out += "} else {" + "\n" + out += "buf.WriteString(`null`)" + "\n" + out += "}" + "\n" + case reflect.Bool: + out += "if " + ptname + " {" + "\n" + ic.q.Write("true") + out += ic.q.GetQueued() + out += "} else {" + "\n" + // Delete 'true' + ic.q.DeleteLast() + out += ic.q.WriteFlush("false") + out += "}" + "\n" + case reflect.Interface: + out += fmt.Sprintf("/* Interface types must use runtime reflection. type=%v kind=%v */\n", typ, typ.Kind()) + out += "err = buf.Encode(" + name + ")" + "\n" + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + case reflect.Map: + out += getMapValue(ic, ptname, typ, ptr, forceString) + case reflect.Struct: + if typ.Name() == "" { + ic.q.Write("{") + ic.q.Write(" ") + out += fmt.Sprintf("/* Inline struct. type=%v kind=%v */\n", typ, typ.Kind()) + newV := reflect.Indirect(reflect.New(typ)).Interface() + fields := extractFields(newV) + + // Output all fields + for _, field := range fields { + // Adjust field name + field.Name = name + "." + field.Name + out += getField(ic, field, "") + } + + if lastConditional(fields) { + out += ic.q.Flush() + out += `buf.Rewind(1)` + "\n" + } else { + ic.q.DeleteLast() + } + out += ic.q.WriteFlush("}") + } else { + out += fmt.Sprintf("/* Struct fall back. type=%v kind=%v */\n", typ, typ.Kind()) + out += ic.q.Flush() + if ptr { + out += "err = buf.Encode(" + name + ")" + "\n" + } else { + // We send pointer to avoid copying entire struct + out += "err = buf.Encode(&" + name + ")" + "\n" + } + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + } + default: + out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind()) + out += "err = buf.Encode(" + name + ")" + "\n" + out += "if err != nil {" + "\n" + out += " return err" + "\n" + out += "}" + "\n" + } + + return out +} + +func getValue(ic *Inception, sf *StructField, prefix string) string { + closequote := false + if sf.ForceString { + switch sf.Typ.Kind() { + case reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr, + reflect.Float32, + reflect.Float64, + reflect.Bool: + ic.q.Write(`"`) + closequote = true + } + } + out := getGetInnerValue(ic, prefix+sf.Name, sf.Typ, sf.Pointer, sf.ForceString) + if closequote { + if sf.Pointer { + out += ic.q.WriteFlush(`"`) + } else { + ic.q.Write(`"`) + } + } + + return out +} + +func p2(v uint32) uint32 { + v-- + v |= v >> 1 + v |= v >> 2 + v |= v >> 4 + v |= v >> 8 + v |= v >> 16 + v++ + return v +} + +func getTypeSize(t reflect.Type) uint32 { + switch t.Kind() { + case reflect.String: + // TODO: consider runtime analysis. + return 32 + case reflect.Array, reflect.Map, reflect.Slice: + // TODO: consider runtime analysis. + return 4 * getTypeSize(t.Elem()) + case reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32: + return 8 + case reflect.Int64, + reflect.Uint64, + reflect.Uintptr: + return 16 + case reflect.Float32, + reflect.Float64: + return 16 + case reflect.Bool: + return 4 + case reflect.Ptr: + return getTypeSize(t.Elem()) + default: + return 16 + } +} + +func getTotalSize(si *StructInfo) uint32 { + rv := uint32(si.Typ.Size()) + for _, f := range si.Fields { + rv += getTypeSize(f.Typ) + } + return rv +} + +func getBufGrowSize(si *StructInfo) uint32 { + + // TOOD(pquerna): automatically calc a better grow size based on history + // of a struct. + return p2(getTotalSize(si)) +} + +func isIntish(t reflect.Type) bool { + if t.Kind() >= reflect.Int && t.Kind() <= reflect.Uintptr { + return true + } + if t.Kind() == reflect.Array || t.Kind() == reflect.Slice || t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { + // base64 special case. + return false + } else { + return isIntish(t.Elem()) + } + } + return false +} + +func getField(ic *Inception, f *StructField, prefix string) string { + out := "" + if f.OmitEmpty { + out += ic.q.Flush() + if f.Pointer { + out += "if " + prefix + f.Name + " != nil {" + "\n" + } + out += getOmitEmpty(ic, f) + } + + if f.Pointer && !f.OmitEmpty { + // Pointer values encode as the value pointed to. A nil pointer encodes as the null JSON object. + out += "if " + prefix + f.Name + " != nil {" + "\n" + } + + // JsonName is already escaped and quoted. + // getInnervalue should flush + ic.q.Write(f.JsonName + ":") + // We save a copy in case we need it + t := ic.q + + out += getValue(ic, f, prefix) + ic.q.Write(",") + + if f.Pointer && !f.OmitEmpty { + out += "} else {" + "\n" + out += t.WriteFlush("null") + out += "}" + "\n" + } + + if f.OmitEmpty { + out += ic.q.Flush() + if f.Pointer { + out += "}" + "\n" + } + out += "}" + "\n" + } + return out +} + +// We check if the last field is conditional. +func lastConditional(fields []*StructField) bool { + if len(fields) > 0 { + f := fields[len(fields)-1] + return f.OmitEmpty + } + return false +} + +func CreateMarshalJSON(ic *Inception, si *StructInfo) error { + conditionalWrites := lastConditional(si.Fields) + out := "" + + out += "// MarshalJSON marshal bytes to json - template\n" + out += `func (j *` + si.Name + `) MarshalJSON() ([]byte, error) {` + "\n" + out += `var buf fflib.Buffer` + "\n" + + out += `if j == nil {` + "\n" + out += ` buf.WriteString("null")` + "\n" + out += " return buf.Bytes(), nil" + "\n" + out += `}` + "\n" + + out += `err := j.MarshalJSONBuf(&buf)` + "\n" + out += `if err != nil {` + "\n" + out += " return nil, err" + "\n" + out += `}` + "\n" + out += `return buf.Bytes(), nil` + "\n" + out += `}` + "\n" + + out += "// MarshalJSONBuf marshal buff to json - template\n" + out += `func (j *` + si.Name + `) MarshalJSONBuf(buf fflib.EncodingBuffer) (error) {` + "\n" + out += ` if j == nil {` + "\n" + out += ` buf.WriteString("null")` + "\n" + out += " return nil" + "\n" + out += ` }` + "\n" + + out += `var err error` + "\n" + out += `var obj []byte` + "\n" + out += `_ = obj` + "\n" + out += `_ = err` + "\n" + + ic.q.Write("{") + + // The extra space is inserted here. + // If nothing is written to the field this will be deleted + // instead of the last comma. + if conditionalWrites || len(si.Fields) == 0 { + ic.q.Write(" ") + } + + for _, f := range si.Fields { + out += getField(ic, f, "j.") + } + + // Handling the last comma is tricky. + // If the last field has omitempty, conditionalWrites is set. + // If something has been written, we delete the last comma, + // by backing up the buffer, otherwise it will delete a space. + if conditionalWrites { + out += ic.q.Flush() + out += `buf.Rewind(1)` + "\n" + } else { + ic.q.DeleteLast() + } + + out += ic.q.WriteFlush("}") + out += `return nil` + "\n" + out += `}` + "\n" + ic.OutputFuncs = append(ic.OutputFuncs, out) + return nil +} |