forked from mirror/go-json
827 lines
23 KiB
Go
827 lines
23 KiB
Go
package encoder
|
|
|
|
import (
|
|
"context"
|
|
"encoding"
|
|
"encoding/json"
|
|
"reflect"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/goccy/go-json/internal/errors"
|
|
"github.com/goccy/go-json/internal/runtime"
|
|
)
|
|
|
|
type marshalerContext interface {
|
|
MarshalJSON(context.Context) ([]byte, error)
|
|
}
|
|
|
|
var (
|
|
marshalJSONType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
|
marshalJSONContextType = reflect.TypeOf((*marshalerContext)(nil)).Elem()
|
|
marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
|
jsonNumberType = reflect.TypeOf(json.Number(""))
|
|
cachedOpcodeSets []*OpcodeSet
|
|
cachedOpcodeMap unsafe.Pointer // map[uintptr]*OpcodeSet
|
|
typeAddr *runtime.TypeAddr
|
|
)
|
|
|
|
func init() {
|
|
typeAddr = runtime.AnalyzeTypeAddr()
|
|
if typeAddr == nil {
|
|
typeAddr = &runtime.TypeAddr{}
|
|
}
|
|
cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift)
|
|
}
|
|
|
|
func loadOpcodeMap() map[uintptr]*OpcodeSet {
|
|
p := atomic.LoadPointer(&cachedOpcodeMap)
|
|
return *(*map[uintptr]*OpcodeSet)(unsafe.Pointer(&p))
|
|
}
|
|
|
|
func storeOpcodeSet(typ uintptr, set *OpcodeSet, m map[uintptr]*OpcodeSet) {
|
|
newOpcodeMap := make(map[uintptr]*OpcodeSet, len(m)+1)
|
|
newOpcodeMap[typ] = set
|
|
|
|
for k, v := range m {
|
|
newOpcodeMap[k] = v
|
|
}
|
|
|
|
atomic.StorePointer(&cachedOpcodeMap, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap)))
|
|
}
|
|
|
|
func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) {
|
|
opcodeMap := loadOpcodeMap()
|
|
if codeSet, exists := opcodeMap[typeptr]; exists {
|
|
return codeSet, nil
|
|
}
|
|
|
|
// noescape trick for header.typ ( reflect.*rtype )
|
|
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
|
|
|
|
noescapeKeyCode, err := compile(&compileContext{
|
|
typ: copiedType,
|
|
structTypeToCode: map[uintptr]*StructCode{},
|
|
structTypeToCodes: map[uintptr]Opcodes{},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
escapeKeyCode, err := compile(&compileContext{
|
|
typ: copiedType,
|
|
structTypeToCode: map[uintptr]*StructCode{},
|
|
structTypeToCodes: map[uintptr]Opcodes{},
|
|
escapeKey: true,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
noescapeKeyCode = copyOpcode(noescapeKeyCode)
|
|
escapeKeyCode = copyOpcode(escapeKeyCode)
|
|
setTotalLengthToInterfaceOp(noescapeKeyCode)
|
|
setTotalLengthToInterfaceOp(escapeKeyCode)
|
|
interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode)
|
|
interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode)
|
|
codeLength := noescapeKeyCode.TotalLength()
|
|
codeSet := &OpcodeSet{
|
|
Type: copiedType,
|
|
NoescapeKeyCode: noescapeKeyCode,
|
|
EscapeKeyCode: escapeKeyCode,
|
|
InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode,
|
|
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
|
|
CodeLength: codeLength,
|
|
EndCode: ToEndCode(interfaceNoescapeKeyCode),
|
|
}
|
|
storeOpcodeSet(typeptr, codeSet, opcodeMap)
|
|
return codeSet, nil
|
|
}
|
|
|
|
func compile(ctx *compileContext) (*Opcode, error) {
|
|
code, err := type2code(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
derefctx := *ctx
|
|
newCtx := &derefctx
|
|
codes := code.ToOpcode(newCtx)
|
|
codes.Last().Next = newEndOp(newCtx)
|
|
linkRecursiveCode(newCtx)
|
|
return codes.First(), nil
|
|
}
|
|
|
|
func implementsMarshalJSON(typ *runtime.Type) bool {
|
|
if !implementsMarshalJSONType(typ) {
|
|
return false
|
|
}
|
|
if typ.Kind() != reflect.Ptr {
|
|
return true
|
|
}
|
|
// type kind is reflect.Ptr
|
|
if !implementsMarshalJSONType(typ.Elem()) {
|
|
return true
|
|
}
|
|
// needs to dereference
|
|
return false
|
|
}
|
|
|
|
func implementsMarshalText(typ *runtime.Type) bool {
|
|
if !typ.Implements(marshalTextType) {
|
|
return false
|
|
}
|
|
if typ.Kind() != reflect.Ptr {
|
|
return true
|
|
}
|
|
// type kind is reflect.Ptr
|
|
if !typ.Elem().Implements(marshalTextType) {
|
|
return true
|
|
}
|
|
// needs to dereference
|
|
return false
|
|
}
|
|
|
|
func convertPtrOp(code *Opcode) OpType {
|
|
ptrHeadOp := code.Op.HeadToPtrHead()
|
|
if code.Op != ptrHeadOp {
|
|
if code.PtrNum > 0 {
|
|
// ptr field and ptr head
|
|
code.PtrNum--
|
|
}
|
|
return ptrHeadOp
|
|
}
|
|
switch code.Op {
|
|
case OpInt:
|
|
return OpIntPtr
|
|
case OpUint:
|
|
return OpUintPtr
|
|
case OpFloat32:
|
|
return OpFloat32Ptr
|
|
case OpFloat64:
|
|
return OpFloat64Ptr
|
|
case OpString:
|
|
return OpStringPtr
|
|
case OpBool:
|
|
return OpBoolPtr
|
|
case OpBytes:
|
|
return OpBytesPtr
|
|
case OpNumber:
|
|
return OpNumberPtr
|
|
case OpArray:
|
|
return OpArrayPtr
|
|
case OpSlice:
|
|
return OpSlicePtr
|
|
case OpMap:
|
|
return OpMapPtr
|
|
case OpMarshalJSON:
|
|
return OpMarshalJSONPtr
|
|
case OpMarshalText:
|
|
return OpMarshalTextPtr
|
|
case OpInterface:
|
|
return OpInterfacePtr
|
|
case OpRecursive:
|
|
return OpRecursivePtr
|
|
}
|
|
return code.Op
|
|
}
|
|
|
|
const intSize = 32 << (^uint(0) >> 63)
|
|
|
|
func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType {
|
|
headType := code.ToHeaderType(tag.IsString)
|
|
if tag.IsOmitEmpty {
|
|
headType = headType.HeadToOmitEmptyHead()
|
|
}
|
|
return headType
|
|
}
|
|
|
|
func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType {
|
|
fieldType := code.ToFieldType(tag.IsString)
|
|
if tag.IsOmitEmpty {
|
|
fieldType = fieldType.FieldToOmitEmptyField()
|
|
}
|
|
return fieldType
|
|
}
|
|
|
|
func isNilableType(typ *runtime.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Ptr:
|
|
return true
|
|
case reflect.Map:
|
|
return true
|
|
case reflect.Func:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func implementsMarshalJSONType(typ *runtime.Type) bool {
|
|
return typ.Implements(marshalJSONType) || typ.Implements(marshalJSONContextType)
|
|
}
|
|
|
|
func isPtrMarshalJSONType(typ *runtime.Type) bool {
|
|
return !implementsMarshalJSONType(typ) && implementsMarshalJSONType(runtime.PtrTo(typ))
|
|
}
|
|
|
|
func isPtrMarshalTextType(typ *runtime.Type) bool {
|
|
return !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType)
|
|
}
|
|
|
|
func linkRecursiveCode(ctx *compileContext) {
|
|
for _, recursive := range *ctx.recursiveCodes {
|
|
typeptr := uintptr(unsafe.Pointer(recursive.Type))
|
|
codes := ctx.structTypeToCodes[typeptr]
|
|
compiled := recursive.Jmp
|
|
compiled.Code = copyOpcode(codes.First())
|
|
code := compiled.Code
|
|
code.End.Next = newEndOp(&compileContext{})
|
|
code.Op = code.Op.PtrHeadToHead()
|
|
|
|
beforeLastCode := code.End
|
|
lastCode := beforeLastCode.Next
|
|
|
|
totalLength := code.TotalLength()
|
|
lastCode.Idx = uint32((totalLength + 1) * uintptrSize)
|
|
lastCode.ElemIdx = lastCode.Idx + uintptrSize
|
|
lastCode.Length = lastCode.Idx + 2*uintptrSize
|
|
code.End.Next.Op = OpRecursiveEnd
|
|
|
|
// extend length to alloc slot for elemIdx + length
|
|
curTotalLength := uintptr(recursive.TotalLength()) + 3
|
|
nextTotalLength := uintptr(totalLength) + 3
|
|
compiled.CurLen = curTotalLength
|
|
compiled.NextLen = nextTotalLength
|
|
compiled.Linked = true
|
|
}
|
|
}
|
|
|
|
func type2code(ctx *compileContext) (Code, error) {
|
|
typ := ctx.typ
|
|
switch {
|
|
case implementsMarshalJSON(typ):
|
|
return compileMarshalJSON(ctx)
|
|
case implementsMarshalText(typ):
|
|
return compileMarshalText(ctx)
|
|
}
|
|
|
|
isPtr := false
|
|
orgType := typ
|
|
if typ.Kind() == reflect.Ptr {
|
|
typ = typ.Elem()
|
|
isPtr = true
|
|
}
|
|
switch {
|
|
case implementsMarshalJSON(typ):
|
|
return compileMarshalJSON(ctx)
|
|
case implementsMarshalText(typ):
|
|
return compileMarshalText(ctx)
|
|
}
|
|
switch typ.Kind() {
|
|
case reflect.Slice:
|
|
ctx := ctx.withType(typ)
|
|
elem := typ.Elem()
|
|
if elem.Kind() == reflect.Uint8 {
|
|
p := runtime.PtrTo(elem)
|
|
if !implementsMarshalJSONType(p) && !p.Implements(marshalTextType) {
|
|
return compileBytes(ctx, isPtr)
|
|
}
|
|
}
|
|
return compileSlice(ctx)
|
|
case reflect.Map:
|
|
if isPtr {
|
|
return compilePtr(ctx.withType(runtime.PtrTo(typ)))
|
|
}
|
|
return compileMap(ctx.withType(typ))
|
|
case reflect.Struct:
|
|
return compileStruct(ctx.withType(typ), isPtr)
|
|
case reflect.Int:
|
|
return compileInt(ctx.withType(typ), isPtr)
|
|
case reflect.Int8:
|
|
return compileInt8(ctx.withType(typ), isPtr)
|
|
case reflect.Int16:
|
|
return compileInt16(ctx.withType(typ), isPtr)
|
|
case reflect.Int32:
|
|
return compileInt32(ctx.withType(typ), isPtr)
|
|
case reflect.Int64:
|
|
return compileInt64(ctx.withType(typ), isPtr)
|
|
case reflect.Uint, reflect.Uintptr:
|
|
return compileUint(ctx.withType(typ), isPtr)
|
|
case reflect.Uint8:
|
|
return compileUint8(ctx.withType(typ), isPtr)
|
|
case reflect.Uint16:
|
|
return compileUint16(ctx.withType(typ), isPtr)
|
|
case reflect.Uint32:
|
|
return compileUint32(ctx.withType(typ), isPtr)
|
|
case reflect.Uint64:
|
|
return compileUint64(ctx.withType(typ), isPtr)
|
|
case reflect.Float32:
|
|
return compileFloat32(ctx.withType(typ), isPtr)
|
|
case reflect.Float64:
|
|
return compileFloat64(ctx.withType(typ), isPtr)
|
|
case reflect.String:
|
|
return compileString(ctx.withType(typ), isPtr)
|
|
case reflect.Bool:
|
|
return compileBool(ctx.withType(typ), isPtr)
|
|
case reflect.Interface:
|
|
return compileInterface(ctx.withType(typ), isPtr)
|
|
default:
|
|
if isPtr && typ.Implements(marshalTextType) {
|
|
typ = orgType
|
|
}
|
|
return type2codeWithPtr(ctx.withType(typ), isPtr)
|
|
}
|
|
}
|
|
|
|
func type2codeWithPtr(ctx *compileContext, isPtr bool) (Code, error) {
|
|
typ := ctx.typ
|
|
switch {
|
|
case implementsMarshalJSON(typ):
|
|
return compileMarshalJSON(ctx)
|
|
case implementsMarshalText(typ):
|
|
return compileMarshalText(ctx)
|
|
}
|
|
switch typ.Kind() {
|
|
case reflect.Ptr:
|
|
return compilePtr(ctx)
|
|
case reflect.Slice:
|
|
elem := typ.Elem()
|
|
if elem.Kind() == reflect.Uint8 {
|
|
p := runtime.PtrTo(elem)
|
|
if !implementsMarshalJSONType(p) && !p.Implements(marshalTextType) {
|
|
return compileBytes(ctx, false)
|
|
}
|
|
}
|
|
return compileSlice(ctx)
|
|
case reflect.Array:
|
|
return compileArray(ctx)
|
|
case reflect.Map:
|
|
return compileMap(ctx)
|
|
case reflect.Struct:
|
|
return compileStruct(ctx, isPtr)
|
|
case reflect.Interface:
|
|
return compileInterface(ctx, false)
|
|
case reflect.Int:
|
|
return compileInt(ctx, false)
|
|
case reflect.Int8:
|
|
return compileInt8(ctx, false)
|
|
case reflect.Int16:
|
|
return compileInt16(ctx, false)
|
|
case reflect.Int32:
|
|
return compileInt32(ctx, false)
|
|
case reflect.Int64:
|
|
return compileInt64(ctx, false)
|
|
case reflect.Uint:
|
|
return compileUint(ctx, false)
|
|
case reflect.Uint8:
|
|
return compileUint8(ctx, false)
|
|
case reflect.Uint16:
|
|
return compileUint16(ctx, false)
|
|
case reflect.Uint32:
|
|
return compileUint32(ctx, false)
|
|
case reflect.Uint64:
|
|
return compileUint64(ctx, false)
|
|
case reflect.Uintptr:
|
|
return compileUint(ctx, false)
|
|
case reflect.Float32:
|
|
return compileFloat32(ctx, false)
|
|
case reflect.Float64:
|
|
return compileFloat64(ctx, false)
|
|
case reflect.String:
|
|
return compileString(ctx, false)
|
|
case reflect.Bool:
|
|
return compileBool(ctx, false)
|
|
}
|
|
return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)}
|
|
}
|
|
|
|
func compileInt(ctx *compileContext, isPtr bool) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileInt8(ctx *compileContext, isPtr bool) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 8, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileInt16(ctx *compileContext, isPtr bool) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 16, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileInt32(ctx *compileContext, isPtr bool) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 32, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileInt64(ctx *compileContext, isPtr bool) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileUint(ctx *compileContext, isPtr bool) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileUint8(ctx *compileContext, isPtr bool) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 8, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileUint16(ctx *compileContext, isPtr bool) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 16, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileUint32(ctx *compileContext, isPtr bool) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 32, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileUint64(ctx *compileContext, isPtr bool) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileFloat32(ctx *compileContext, isPtr bool) (*FloatCode, error) {
|
|
return &FloatCode{typ: ctx.typ, bitSize: 32, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileFloat64(ctx *compileContext, isPtr bool) (*FloatCode, error) {
|
|
return &FloatCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileString(ctx *compileContext, isPtr bool) (*StringCode, error) {
|
|
return &StringCode{typ: ctx.typ, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileBool(ctx *compileContext, isPtr bool) (*BoolCode, error) {
|
|
return &BoolCode{typ: ctx.typ, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileIntString(ctx *compileContext) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil
|
|
}
|
|
|
|
func compileInt8String(ctx *compileContext) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 8, isString: true}, nil
|
|
}
|
|
|
|
func compileInt16String(ctx *compileContext) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 16, isString: true}, nil
|
|
}
|
|
|
|
func compileInt32String(ctx *compileContext) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 32, isString: true}, nil
|
|
}
|
|
|
|
func compileInt64String(ctx *compileContext) (*IntCode, error) {
|
|
return &IntCode{typ: ctx.typ, bitSize: 64, isString: true}, nil
|
|
}
|
|
|
|
func compileUintString(ctx *compileContext) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil
|
|
}
|
|
|
|
func compileUint8String(ctx *compileContext) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 8, isString: true}, nil
|
|
}
|
|
|
|
func compileUint16String(ctx *compileContext) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 16, isString: true}, nil
|
|
}
|
|
|
|
func compileUint32String(ctx *compileContext) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 32, isString: true}, nil
|
|
}
|
|
|
|
func compileUint64String(ctx *compileContext) (*UintCode, error) {
|
|
return &UintCode{typ: ctx.typ, bitSize: 64, isString: true}, nil
|
|
}
|
|
|
|
func compileSlice(ctx *compileContext) (*SliceCode, error) {
|
|
elem := ctx.typ.Elem()
|
|
code, err := compileListElem(ctx.withType(elem))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if code.Type() == CodeTypeStruct {
|
|
structCode := code.(*StructCode)
|
|
structCode.enableIndirect()
|
|
}
|
|
return &SliceCode{typ: ctx.typ, value: code}, nil
|
|
}
|
|
|
|
func compileArray(ctx *compileContext) (*ArrayCode, error) {
|
|
typ := ctx.typ
|
|
elem := typ.Elem()
|
|
code, err := compileListElem(ctx.withType(elem))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if code.Type() == CodeTypeStruct {
|
|
structCode := code.(*StructCode)
|
|
structCode.enableIndirect()
|
|
}
|
|
return &ArrayCode{typ: ctx.typ, value: code}, nil
|
|
}
|
|
|
|
func compileMap(ctx *compileContext) (*MapCode, error) {
|
|
typ := ctx.typ
|
|
keyCode, err := compileMapKey(ctx.withType(typ.Key()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
valueCode, err := compileMapValue(ctx.withType(typ.Elem()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if valueCode.Type() == CodeTypeStruct {
|
|
structCode := valueCode.(*StructCode)
|
|
structCode.enableIndirect()
|
|
}
|
|
return &MapCode{typ: ctx.typ, key: keyCode, value: valueCode}, nil
|
|
}
|
|
|
|
func compileBytes(ctx *compileContext, isPtr bool) (*BytesCode, error) {
|
|
return &BytesCode{typ: ctx.typ, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileInterface(ctx *compileContext, isPtr bool) (*InterfaceCode, error) {
|
|
return &InterfaceCode{typ: ctx.typ, isPtr: isPtr}, nil
|
|
}
|
|
|
|
func compileMarshalJSON(ctx *compileContext) (*MarshalJSONCode, error) {
|
|
return &MarshalJSONCode{typ: ctx.typ}, nil
|
|
}
|
|
|
|
func compileMarshalText(ctx *compileContext) (*MarshalTextCode, error) {
|
|
return &MarshalTextCode{typ: ctx.typ}, nil
|
|
}
|
|
|
|
func compilePtr(ctx *compileContext) (*PtrCode, error) {
|
|
code, err := type2codeWithPtr(ctx.withType(ctx.typ.Elem()), true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ptr, ok := code.(*PtrCode)
|
|
if ok {
|
|
return &PtrCode{typ: ctx.typ, value: ptr.value, ptrNum: ptr.ptrNum + 1}, nil
|
|
}
|
|
return &PtrCode{typ: ctx.typ, value: code, ptrNum: 1}, nil
|
|
}
|
|
|
|
func compileListElem(ctx *compileContext) (Code, error) {
|
|
typ := ctx.typ
|
|
switch {
|
|
case isPtrMarshalJSONType(typ):
|
|
return compileMarshalJSON(ctx)
|
|
case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType):
|
|
return compileMarshalText(ctx)
|
|
case typ.Kind() == reflect.Map:
|
|
return compilePtr(ctx.withType(runtime.PtrTo(typ)))
|
|
default:
|
|
code, err := type2codeWithPtr(ctx, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ptr, ok := code.(*PtrCode)
|
|
if ok {
|
|
if ptr.value.Type() == CodeTypeMap {
|
|
ptr.ptrNum++
|
|
}
|
|
}
|
|
return code, nil
|
|
}
|
|
}
|
|
|
|
func compileMapKey(ctx *compileContext) (Code, error) {
|
|
typ := ctx.typ
|
|
switch {
|
|
case implementsMarshalJSON(typ):
|
|
return compileMarshalJSON(ctx)
|
|
case implementsMarshalText(typ):
|
|
return compileMarshalText(ctx)
|
|
}
|
|
switch typ.Kind() {
|
|
case reflect.Ptr:
|
|
return compilePtr(ctx)
|
|
case reflect.String:
|
|
return compileString(ctx, false)
|
|
case reflect.Int:
|
|
return compileIntString(ctx)
|
|
case reflect.Int8:
|
|
return compileInt8String(ctx)
|
|
case reflect.Int16:
|
|
return compileInt16String(ctx)
|
|
case reflect.Int32:
|
|
return compileInt32String(ctx)
|
|
case reflect.Int64:
|
|
return compileInt64String(ctx)
|
|
case reflect.Uint:
|
|
return compileUintString(ctx)
|
|
case reflect.Uint8:
|
|
return compileUint8String(ctx)
|
|
case reflect.Uint16:
|
|
return compileUint16String(ctx)
|
|
case reflect.Uint32:
|
|
return compileUint32String(ctx)
|
|
case reflect.Uint64:
|
|
return compileUint64String(ctx)
|
|
case reflect.Uintptr:
|
|
return compileUintString(ctx)
|
|
}
|
|
return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)}
|
|
}
|
|
|
|
func compileMapValue(ctx *compileContext) (Code, error) {
|
|
switch ctx.typ.Kind() {
|
|
case reflect.Map:
|
|
return compilePtr(ctx.withType(runtime.PtrTo(ctx.typ)))
|
|
default:
|
|
code, err := type2codeWithPtr(ctx, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ptr, ok := code.(*PtrCode)
|
|
if ok {
|
|
if ptr.value.Type() == CodeTypeMap {
|
|
ptr.ptrNum++
|
|
}
|
|
}
|
|
return code, nil
|
|
}
|
|
}
|
|
|
|
func compileStruct(ctx *compileContext, isPtr bool) (*StructCode, error) {
|
|
typ := ctx.typ
|
|
typeptr := uintptr(unsafe.Pointer(typ))
|
|
if code, exists := ctx.structTypeToCode[typeptr]; exists {
|
|
derefCode := *code
|
|
derefCode.isRecursive = true
|
|
return &derefCode, nil
|
|
}
|
|
indirect := runtime.IfaceIndir(typ)
|
|
code := &StructCode{typ: typ, isPtr: isPtr, isIndirect: indirect}
|
|
ctx.structTypeToCode[typeptr] = code
|
|
|
|
fieldNum := typ.NumField()
|
|
tags := typeToStructTags(typ)
|
|
fields := []*StructFieldCode{}
|
|
for i, tag := range tags {
|
|
isOnlyOneFirstField := i == 0 && fieldNum == 1
|
|
field, err := code.compileStructField(ctx, tag, isPtr, isOnlyOneFirstField)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if field.isAnonymous {
|
|
structCode := field.getAnonymousStruct()
|
|
if structCode != nil {
|
|
structCode.removeFieldsByTags(tags)
|
|
if isAssignableIndirect(field, isPtr) {
|
|
if indirect {
|
|
structCode.isIndirect = true
|
|
} else {
|
|
structCode.isIndirect = false
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
structCode := field.getStruct()
|
|
if structCode != nil {
|
|
if indirect {
|
|
// if parent is indirect type, set child indirect property to true
|
|
structCode.isIndirect = true
|
|
} else {
|
|
// if parent is not indirect type, set child indirect property to false.
|
|
// but if parent's indirect is false and isPtr is true, then indirect must be true.
|
|
// Do this only if indirectConversion is enabled at the end of compileStruct.
|
|
structCode.isIndirect = false
|
|
}
|
|
}
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
fieldMap := getFieldMap(fields)
|
|
duplicatedFieldMap := getDuplicatedFieldMap(fieldMap)
|
|
code.fields = filteredDuplicatedFields(fields, duplicatedFieldMap)
|
|
if !code.disableIndirectConversion && !indirect && isPtr {
|
|
code.enableIndirect()
|
|
}
|
|
delete(ctx.structTypeToCode, typeptr)
|
|
return code, nil
|
|
}
|
|
|
|
func getFieldMap(fields []*StructFieldCode) map[string][]*StructFieldCode {
|
|
fieldMap := map[string][]*StructFieldCode{}
|
|
for _, field := range fields {
|
|
if field.isAnonymous {
|
|
for k, v := range getAnonymousFieldMap(field) {
|
|
fieldMap[k] = append(fieldMap[k], v...)
|
|
}
|
|
continue
|
|
}
|
|
fieldMap[field.key] = append(fieldMap[field.key], field)
|
|
}
|
|
return fieldMap
|
|
}
|
|
|
|
func getAnonymousFieldMap(field *StructFieldCode) map[string][]*StructFieldCode {
|
|
fieldMap := map[string][]*StructFieldCode{}
|
|
structCode := field.getAnonymousStruct()
|
|
if structCode == nil || structCode.isRecursive {
|
|
fieldMap[field.key] = append(fieldMap[field.key], field)
|
|
return fieldMap
|
|
}
|
|
for k, v := range getFieldMapFromAnonymousParent(structCode.fields) {
|
|
fieldMap[k] = append(fieldMap[k], v...)
|
|
}
|
|
return fieldMap
|
|
}
|
|
|
|
func getFieldMapFromAnonymousParent(fields []*StructFieldCode) map[string][]*StructFieldCode {
|
|
fieldMap := map[string][]*StructFieldCode{}
|
|
for _, field := range fields {
|
|
if field.isAnonymous {
|
|
for k, v := range getAnonymousFieldMap(field) {
|
|
// Do not handle tagged key when embedding more than once
|
|
for _, vv := range v {
|
|
vv.isTaggedKey = false
|
|
}
|
|
fieldMap[k] = append(fieldMap[k], v...)
|
|
}
|
|
continue
|
|
}
|
|
fieldMap[field.key] = append(fieldMap[field.key], field)
|
|
}
|
|
return fieldMap
|
|
}
|
|
|
|
func getDuplicatedFieldMap(fieldMap map[string][]*StructFieldCode) map[*StructFieldCode]struct{} {
|
|
duplicatedFieldMap := map[*StructFieldCode]struct{}{}
|
|
for _, fields := range fieldMap {
|
|
if len(fields) == 1 {
|
|
continue
|
|
}
|
|
if isTaggedKeyOnly(fields) {
|
|
for _, field := range fields {
|
|
if field.isTaggedKey {
|
|
continue
|
|
}
|
|
duplicatedFieldMap[field] = struct{}{}
|
|
}
|
|
} else {
|
|
for _, field := range fields {
|
|
duplicatedFieldMap[field] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
return duplicatedFieldMap
|
|
}
|
|
|
|
func filteredDuplicatedFields(fields []*StructFieldCode, duplicatedFieldMap map[*StructFieldCode]struct{}) []*StructFieldCode {
|
|
filteredFields := make([]*StructFieldCode, 0, len(fields))
|
|
for _, field := range fields {
|
|
if field.isAnonymous {
|
|
structCode := field.getAnonymousStruct()
|
|
if structCode != nil && !structCode.isRecursive {
|
|
structCode.fields = filteredDuplicatedFields(structCode.fields, duplicatedFieldMap)
|
|
if len(structCode.fields) > 0 {
|
|
filteredFields = append(filteredFields, field)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if _, exists := duplicatedFieldMap[field]; exists {
|
|
continue
|
|
}
|
|
filteredFields = append(filteredFields, field)
|
|
}
|
|
return filteredFields
|
|
}
|
|
|
|
func isTaggedKeyOnly(fields []*StructFieldCode) bool {
|
|
var taggedKeyFieldCount int
|
|
for _, field := range fields {
|
|
if field.isTaggedKey {
|
|
taggedKeyFieldCount++
|
|
}
|
|
}
|
|
return taggedKeyFieldCount == 1
|
|
}
|
|
|
|
func typeToStructTags(typ *runtime.Type) runtime.StructTags {
|
|
tags := runtime.StructTags{}
|
|
fieldNum := typ.NumField()
|
|
for i := 0; i < fieldNum; i++ {
|
|
field := typ.Field(i)
|
|
if runtime.IsIgnoredStructField(field) {
|
|
continue
|
|
}
|
|
tags = append(tags, runtime.StructTagFromField(field))
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// *struct{ field T } => struct { field *T }
|
|
// func (*T) MarshalJSON() ([]byte, error)
|
|
func isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool {
|
|
return isIndirectSpecialCase && !isNilableType(typ) && isPtrMarshalJSONType(typ)
|
|
}
|
|
|
|
// *struct{ field T } => struct { field *T }
|
|
// func (*T) MarshalText() ([]byte, error)
|
|
func isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool {
|
|
return isIndirectSpecialCase && !isNilableType(typ) && isPtrMarshalTextType(typ)
|
|
}
|