GoDoxy/pkg/json/struct.go

148 lines
3.2 KiB
Go

package json
import (
"fmt"
"reflect"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type field struct {
quotedNameWithCol string
index int
inner []*field
hasInner bool
isPtr bool
omitEmpty bool // true when json tag is "omitempty" or field is pointer to anonymous struct
checkEmpty checkEmptyFunc
marshal marshalFunc
}
func (f *field) appendKV(v reflect.Value, buf []byte) []byte {
return f.marshal(v, append(buf, f.quotedNameWithCol...))
}
const (
tagOmitEmpty = "omitempty"
tagString = "string" // https://pkg.go.dev/github.com/yusing/go-proxy/pkg/json#Marshal
tagByteSize = "byte_size"
tagUnixTime = "unix_time"
tagUseMarshaler = "use_marshaler"
)
func appendStruct(v reflect.Value, buf []byte) []byte {
if res, ok := appendWithCachedFunc(v, buf); ok {
return res
}
if res, ok := appendWithCustomMarshaler(v, buf); ok {
return res
}
t := v.Type()
fields := flattenFields(t)
marshalFn := func(v reflect.Value, buf []byte) []byte {
return appendFields(v, fields, buf)
}
cacheMarshalFunc(t, marshalFn)
return marshalFn(v, buf)
}
func appendFields(v reflect.Value, fields []*field, buf []byte) []byte {
buf = append(buf, '{')
oldN := len(buf)
for _, f := range fields {
cur := v.Field(f.index)
if f.omitEmpty && f.checkEmpty(cur) {
continue
}
if !f.hasInner {
buf = f.appendKV(cur, buf)
buf = append(buf, ',')
continue
}
if f.isPtr {
cur = cur.Elem()
}
for _, inner := range f.inner {
buf = inner.appendKV(cur.Field(inner.index), buf)
buf = append(buf, ',')
}
}
n := len(buf)
if oldN != n {
buf = buf[:n-1]
}
return append(buf, '}')
}
func flattenFields(t reflect.Type) []*field {
n := t.NumField()
fields := make([]*field, 0, n)
for i := range n {
structField := t.Field(i)
if !structField.IsExported() {
continue
}
t := structField.Type
kind := t.Kind()
f := &field{
index: i,
isPtr: kind == reflect.Pointer,
}
jsonTag, ok := structField.Tag.Lookup("json")
if ok {
if jsonTag == "-" {
continue
}
parts := strutils.SplitComma(jsonTag)
if len(parts) > 1 {
switch parts[1] {
case tagOmitEmpty:
f.omitEmpty = true
f.checkEmpty = checkEmptyFuncs[kind]
case tagString:
f.marshal = appendStringRepr
case tagByteSize:
f.marshal = appendByteSize
case tagUnixTime:
f.marshal = appendUnixTime
case tagUseMarshaler:
f.marshal = mustAppendWithCustomMarshaler
default:
panic(fmt.Errorf("unknown json tag: %s", parts[1]))
}
f.quotedNameWithCol = parts[0]
} else {
f.quotedNameWithCol = jsonTag
}
}
if f.quotedNameWithCol == "" { // e.g. json:",omitempty"
f.quotedNameWithCol = structField.Name
}
if f.marshal == nil {
f.marshal = appendMarshal
}
if structField.Anonymous {
if kind == reflect.Pointer {
t = t.Elem()
kind = t.Kind()
f.isPtr = true
f.omitEmpty = true
f.checkEmpty = checkEmptyFuncs[kind]
}
if kind == reflect.Struct {
f.inner = flattenFields(t)
f.hasInner = len(f.inner) > 0
}
}
fields = append(fields, f)
quotedNameWithCol := AppendString(nil, f.quotedNameWithCol)
f.quotedNameWithCol = string(quotedNameWithCol) + ":"
}
return fields
}