package encoder
import (
"context"
"encoding"
"encoding/json"
"reflect"
"sync"
"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
typeAddr *runtime .TypeAddr
initEncoderOnce sync .Once
)
func initEncoder() {
initEncoderOnce .Do (func () {
typeAddr = runtime .AnalyzeTypeAddr ()
if typeAddr == nil {
typeAddr = &runtime .TypeAddr {}
}
cachedOpcodeSets = make ([]*OpcodeSet , typeAddr .AddrRange >>typeAddr .AddrShift +1 )
})
}
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
}
codeSet , err := newCompiler ().compile (typeptr )
if err != nil {
return nil , err
}
storeOpcodeSet (typeptr , codeSet , opcodeMap )
return codeSet , nil
}
func getFilteredCodeSetIfNeeded(ctx *RuntimeContext , codeSet *OpcodeSet ) (*OpcodeSet , error ) {
if (ctx .Option .Flag & ContextOption ) == 0 {
return codeSet , nil
}
query := FieldQueryFromContext (ctx .Option .Context )
if query == nil {
return codeSet , nil
}
ctx .Option .Flag |= FieldQueryOption
cacheCodeSet := codeSet .getQueryCache (query .Hash ())
if cacheCodeSet != nil {
return cacheCodeSet , nil
}
queryCodeSet , err := newCompiler ().codeToOpcodeSet (codeSet .Type , codeSet .Code .Filter (query ))
if err != nil {
return nil , err
}
codeSet .setQueryCache (query .Hash (), queryCodeSet )
return queryCodeSet , nil
}
type Compiler struct {
structTypeToCode map [uintptr ]*StructCode
}
func newCompiler() *Compiler {
return &Compiler {
structTypeToCode : map [uintptr ]*StructCode {},
}
}
func (c *Compiler ) compile (typeptr uintptr ) (*OpcodeSet , error ) {
typ := *(**runtime .Type )(unsafe .Pointer (&typeptr ))
code , err := c .typeToCode (typ )
if err != nil {
return nil , err
}
return c .codeToOpcodeSet (typ , code )
}
func (c *Compiler ) codeToOpcodeSet (typ *runtime .Type , code Code ) (*OpcodeSet , error ) {
noescapeKeyCode := c .codeToOpcode (&compileContext {
structTypeToCodes : map [uintptr ]Opcodes {},
recursiveCodes : &Opcodes {},
}, typ , code )
if err := noescapeKeyCode .Validate (); err != nil {
return nil , err
}
escapeKeyCode := c .codeToOpcode (&compileContext {
structTypeToCodes : map [uintptr ]Opcodes {},
recursiveCodes : &Opcodes {},
escapeKey : true ,
}, typ , code )
noescapeKeyCode = copyOpcode (noescapeKeyCode )
escapeKeyCode = copyOpcode (escapeKeyCode )
setTotalLengthToInterfaceOp (noescapeKeyCode )
setTotalLengthToInterfaceOp (escapeKeyCode )
interfaceNoescapeKeyCode := copyToInterfaceOpcode (noescapeKeyCode )
interfaceEscapeKeyCode := copyToInterfaceOpcode (escapeKeyCode )
codeLength := noescapeKeyCode .TotalLength ()
return &OpcodeSet {
Type : typ ,
NoescapeKeyCode : noescapeKeyCode ,
EscapeKeyCode : escapeKeyCode ,
InterfaceNoescapeKeyCode : interfaceNoescapeKeyCode ,
InterfaceEscapeKeyCode : interfaceEscapeKeyCode ,
CodeLength : codeLength ,
EndCode : ToEndCode (interfaceNoescapeKeyCode ),
Code : code ,
QueryCache : map [string ]*OpcodeSet {},
}, nil
}
func (c *Compiler ) typeToCode (typ *runtime .Type ) (Code , error ) {
switch {
case c .implementsMarshalJSON (typ ):
return c .marshalJSONCode (typ )
case c .implementsMarshalText (typ ):
return c .marshalTextCode (typ )
}
isPtr := false
orgType := typ
if typ .Kind () == reflect .Ptr {
typ = typ .Elem ()
isPtr = true
}
switch {
case c .implementsMarshalJSON (typ ):
return c .marshalJSONCode (orgType )
case c .implementsMarshalText (typ ):
return c .marshalTextCode (orgType )
}
switch typ .Kind () {
case reflect .Slice :
elem := typ .Elem ()
if elem .Kind () == reflect .Uint8 {
p := runtime .PtrTo (elem )
if !c .implementsMarshalJSONType (p ) && !p .Implements (marshalTextType ) {
return c .bytesCode (typ , isPtr )
}
}
return c .sliceCode (typ )
case reflect .Map :
if isPtr {
return c .ptrCode (runtime .PtrTo (typ ))
}
return c .mapCode (typ )
case reflect .Struct :
return c .structCode (typ , isPtr )
case reflect .Int :
return c .intCode (typ , isPtr )
case reflect .Int8 :
return c .int8Code (typ , isPtr )
case reflect .Int16 :
return c .int16Code (typ , isPtr )
case reflect .Int32 :
return c .int32Code (typ , isPtr )
case reflect .Int64 :
return c .int64Code (typ , isPtr )
case reflect .Uint , reflect .Uintptr :
return c .uintCode (typ , isPtr )
case reflect .Uint8 :
return c .uint8Code (typ , isPtr )
case reflect .Uint16 :
return c .uint16Code (typ , isPtr )
case reflect .Uint32 :
return c .uint32Code (typ , isPtr )
case reflect .Uint64 :
return c .uint64Code (typ , isPtr )
case reflect .Float32 :
return c .float32Code (typ , isPtr )
case reflect .Float64 :
return c .float64Code (typ , isPtr )
case reflect .String :
return c .stringCode (typ , isPtr )
case reflect .Bool :
return c .boolCode (typ , isPtr )
case reflect .Interface :
return c .interfaceCode (typ , isPtr )
default :
if isPtr && typ .Implements (marshalTextType ) {
typ = orgType
}
return c .typeToCodeWithPtr (typ , isPtr )
}
}
func (c *Compiler ) typeToCodeWithPtr (typ *runtime .Type , isPtr bool ) (Code , error ) {
switch {
case c .implementsMarshalJSON (typ ):
return c .marshalJSONCode (typ )
case c .implementsMarshalText (typ ):
return c .marshalTextCode (typ )
}
switch typ .Kind () {
case reflect .Ptr :
return c .ptrCode (typ )
case reflect .Slice :
elem := typ .Elem ()
if elem .Kind () == reflect .Uint8 {
p := runtime .PtrTo (elem )
if !c .implementsMarshalJSONType (p ) && !p .Implements (marshalTextType ) {
return c .bytesCode (typ , false )
}
}
return c .sliceCode (typ )
case reflect .Array :
return c .arrayCode (typ )
case reflect .Map :
return c .mapCode (typ )
case reflect .Struct :
return c .structCode (typ , isPtr )
case reflect .Interface :
return c .interfaceCode (typ , false )
case reflect .Int :
return c .intCode (typ , false )
case reflect .Int8 :
return c .int8Code (typ , false )
case reflect .Int16 :
return c .int16Code (typ , false )
case reflect .Int32 :
return c .int32Code (typ , false )
case reflect .Int64 :
return c .int64Code (typ , false )
case reflect .Uint :
return c .uintCode (typ , false )
case reflect .Uint8 :
return c .uint8Code (typ , false )
case reflect .Uint16 :
return c .uint16Code (typ , false )
case reflect .Uint32 :
return c .uint32Code (typ , false )
case reflect .Uint64 :
return c .uint64Code (typ , false )
case reflect .Uintptr :
return c .uintCode (typ , false )
case reflect .Float32 :
return c .float32Code (typ , false )
case reflect .Float64 :
return c .float64Code (typ , false )
case reflect .String :
return c .stringCode (typ , false )
case reflect .Bool :
return c .boolCode (typ , false )
}
return nil , &errors .UnsupportedTypeError {Type : runtime .RType2Type (typ )}
}
const intSize = 32 << (^uint (0 ) >> 63 )
func (c *Compiler ) intCode (typ *runtime .Type , isPtr bool ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : intSize , isPtr : isPtr }, nil
}
func (c *Compiler ) int8Code (typ *runtime .Type , isPtr bool ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 8 , isPtr : isPtr }, nil
}
func (c *Compiler ) int16Code (typ *runtime .Type , isPtr bool ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 16 , isPtr : isPtr }, nil
}
func (c *Compiler ) int32Code (typ *runtime .Type , isPtr bool ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 32 , isPtr : isPtr }, nil
}
func (c *Compiler ) int64Code (typ *runtime .Type , isPtr bool ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 64 , isPtr : isPtr }, nil
}
func (c *Compiler ) uintCode (typ *runtime .Type , isPtr bool ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : intSize , isPtr : isPtr }, nil
}
func (c *Compiler ) uint8Code (typ *runtime .Type , isPtr bool ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 8 , isPtr : isPtr }, nil
}
func (c *Compiler ) uint16Code (typ *runtime .Type , isPtr bool ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 16 , isPtr : isPtr }, nil
}
func (c *Compiler ) uint32Code (typ *runtime .Type , isPtr bool ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 32 , isPtr : isPtr }, nil
}
func (c *Compiler ) uint64Code (typ *runtime .Type , isPtr bool ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 64 , isPtr : isPtr }, nil
}
func (c *Compiler ) float32Code (typ *runtime .Type , isPtr bool ) (*FloatCode , error ) {
return &FloatCode {typ : typ , bitSize : 32 , isPtr : isPtr }, nil
}
func (c *Compiler ) float64Code (typ *runtime .Type , isPtr bool ) (*FloatCode , error ) {
return &FloatCode {typ : typ , bitSize : 64 , isPtr : isPtr }, nil
}
func (c *Compiler ) stringCode (typ *runtime .Type , isPtr bool ) (*StringCode , error ) {
return &StringCode {typ : typ , isPtr : isPtr }, nil
}
func (c *Compiler ) boolCode (typ *runtime .Type , isPtr bool ) (*BoolCode , error ) {
return &BoolCode {typ : typ , isPtr : isPtr }, nil
}
func (c *Compiler ) intStringCode (typ *runtime .Type ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : intSize , isString : true }, nil
}
func (c *Compiler ) int8StringCode (typ *runtime .Type ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 8 , isString : true }, nil
}
func (c *Compiler ) int16StringCode (typ *runtime .Type ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 16 , isString : true }, nil
}
func (c *Compiler ) int32StringCode (typ *runtime .Type ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 32 , isString : true }, nil
}
func (c *Compiler ) int64StringCode (typ *runtime .Type ) (*IntCode , error ) {
return &IntCode {typ : typ , bitSize : 64 , isString : true }, nil
}
func (c *Compiler ) uintStringCode (typ *runtime .Type ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : intSize , isString : true }, nil
}
func (c *Compiler ) uint8StringCode (typ *runtime .Type ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 8 , isString : true }, nil
}
func (c *Compiler ) uint16StringCode (typ *runtime .Type ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 16 , isString : true }, nil
}
func (c *Compiler ) uint32StringCode (typ *runtime .Type ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 32 , isString : true }, nil
}
func (c *Compiler ) uint64StringCode (typ *runtime .Type ) (*UintCode , error ) {
return &UintCode {typ : typ , bitSize : 64 , isString : true }, nil
}
func (c *Compiler ) bytesCode (typ *runtime .Type , isPtr bool ) (*BytesCode , error ) {
return &BytesCode {typ : typ , isPtr : isPtr }, nil
}
func (c *Compiler ) interfaceCode (typ *runtime .Type , isPtr bool ) (*InterfaceCode , error ) {
return &InterfaceCode {typ : typ , isPtr : isPtr }, nil
}
func (c *Compiler ) marshalJSONCode (typ *runtime .Type ) (*MarshalJSONCode , error ) {
return &MarshalJSONCode {
typ : typ ,
isAddrForMarshaler : c .isPtrMarshalJSONType (typ ),
isNilableType : c .isNilableType (typ ),
isMarshalerContext : typ .Implements (marshalJSONContextType ) || runtime .PtrTo (typ ).Implements (marshalJSONContextType ),
}, nil
}
func (c *Compiler ) marshalTextCode (typ *runtime .Type ) (*MarshalTextCode , error ) {
return &MarshalTextCode {
typ : typ ,
isAddrForMarshaler : c .isPtrMarshalTextType (typ ),
isNilableType : c .isNilableType (typ ),
}, nil
}
func (c *Compiler ) ptrCode (typ *runtime .Type ) (*PtrCode , error ) {
code , err := c .typeToCodeWithPtr (typ .Elem (), true )
if err != nil {
return nil , err
}
ptr , ok := code .(*PtrCode )
if ok {
return &PtrCode {typ : typ , value : ptr .value , ptrNum : ptr .ptrNum + 1 }, nil
}
return &PtrCode {typ : typ , value : code , ptrNum : 1 }, nil
}
func (c *Compiler ) sliceCode (typ *runtime .Type ) (*SliceCode , error ) {
elem := typ .Elem ()
code , err := c .listElemCode (elem )
if err != nil {
return nil , err
}
if code .Kind () == CodeKindStruct {
structCode := code .(*StructCode )
structCode .enableIndirect ()
}
return &SliceCode {typ : typ , value : code }, nil
}
func (c *Compiler ) arrayCode (typ *runtime .Type ) (*ArrayCode , error ) {
elem := typ .Elem ()
code , err := c .listElemCode (elem )
if err != nil {
return nil , err
}
if code .Kind () == CodeKindStruct {
structCode := code .(*StructCode )
structCode .enableIndirect ()
}
return &ArrayCode {typ : typ , value : code }, nil
}
func (c *Compiler ) mapCode (typ *runtime .Type ) (*MapCode , error ) {
keyCode , err := c .mapKeyCode (typ .Key ())
if err != nil {
return nil , err
}
valueCode , err := c .mapValueCode (typ .Elem ())
if err != nil {
return nil , err
}
if valueCode .Kind () == CodeKindStruct {
structCode := valueCode .(*StructCode )
structCode .enableIndirect ()
}
return &MapCode {typ : typ , key : keyCode , value : valueCode }, nil
}
func (c *Compiler ) listElemCode (typ *runtime .Type ) (Code , error ) {
switch {
case c .implementsMarshalJSONType (typ ) || c .implementsMarshalJSONType (runtime .PtrTo (typ )):
return c .marshalJSONCode (typ )
case !typ .Implements (marshalTextType ) && runtime .PtrTo (typ ).Implements (marshalTextType ):
return c .marshalTextCode (typ )
case typ .Kind () == reflect .Map :
return c .ptrCode (runtime .PtrTo (typ ))
default :
code , err := c .typeToCodeWithPtr (typ , true )
if err != nil {
return nil , err
}
ptr , ok := code .(*PtrCode )
if ok {
if ptr .value .Kind () == CodeKindMap {
ptr .ptrNum ++
}
}
return code , nil
}
}
func (c *Compiler ) mapKeyCode (typ *runtime .Type ) (Code , error ) {
switch {
case c .implementsMarshalText (typ ):
return c .marshalTextCode (typ )
}
switch typ .Kind () {
case reflect .Ptr :
return c .ptrCode (typ )
case reflect .String :
return c .stringCode (typ , false )
case reflect .Int :
return c .intStringCode (typ )
case reflect .Int8 :
return c .int8StringCode (typ )
case reflect .Int16 :
return c .int16StringCode (typ )
case reflect .Int32 :
return c .int32StringCode (typ )
case reflect .Int64 :
return c .int64StringCode (typ )
case reflect .Uint :
return c .uintStringCode (typ )
case reflect .Uint8 :
return c .uint8StringCode (typ )
case reflect .Uint16 :
return c .uint16StringCode (typ )
case reflect .Uint32 :
return c .uint32StringCode (typ )
case reflect .Uint64 :
return c .uint64StringCode (typ )
case reflect .Uintptr :
return c .uintStringCode (typ )
}
return nil , &errors .UnsupportedTypeError {Type : runtime .RType2Type (typ )}
}
func (c *Compiler ) mapValueCode (typ *runtime .Type ) (Code , error ) {
switch typ .Kind () {
case reflect .Map :
return c .ptrCode (runtime .PtrTo (typ ))
default :
code , err := c .typeToCodeWithPtr (typ , false )
if err != nil {
return nil , err
}
ptr , ok := code .(*PtrCode )
if ok {
if ptr .value .Kind () == CodeKindMap {
ptr .ptrNum ++
}
}
return code , nil
}
}
func (c *Compiler ) structCode (typ *runtime .Type , isPtr bool ) (*StructCode , error ) {
typeptr := uintptr (unsafe .Pointer (typ ))
if code , exists := c .structTypeToCode [typeptr ]; exists {
derefCode := *code
derefCode .isRecursive = true
return &derefCode , nil
}
indirect := runtime .IfaceIndir (typ )
code := &StructCode {typ : typ , isPtr : isPtr , isIndirect : indirect }
c .structTypeToCode [typeptr ] = code
fieldNum := typ .NumField ()
tags := c .typeToStructTags (typ )
fields := []*StructFieldCode {}
for i , tag := range tags {
isOnlyOneFirstField := i == 0 && fieldNum == 1
field , err := c .structFieldCode (code , tag , isPtr , isOnlyOneFirstField )
if err != nil {
return nil , err
}
if field .isAnonymous {
structCode := field .getAnonymousStruct ()
if structCode != nil {
structCode .removeFieldsByTags (tags )
if c .isAssignableIndirect (field , isPtr ) {
if indirect {
structCode .isIndirect = true
} else {
structCode .isIndirect = false
}
}
}
} else {
structCode := field .getStruct ()
if structCode != nil {
if indirect {
structCode .isIndirect = true
} else {
structCode .isIndirect = false
}
}
}
fields = append (fields , field )
}
fieldMap := c .getFieldMap (fields )
duplicatedFieldMap := c .getDuplicatedFieldMap (fieldMap )
code .fields = c .filteredDuplicatedFields (fields , duplicatedFieldMap )
if !code .disableIndirectConversion && !indirect && isPtr {
code .enableIndirect ()
}
delete (c .structTypeToCode , typeptr )
return code , nil
}
func toElemType(t *runtime .Type ) *runtime .Type {
for t .Kind () == reflect .Ptr {
t = t .Elem ()
}
return t
}
func (c *Compiler ) structFieldCode (structCode *StructCode , tag *runtime .StructTag , isPtr , isOnlyOneFirstField bool ) (*StructFieldCode , error ) {
field := tag .Field
fieldType := runtime .Type2RType (field .Type )
isIndirectSpecialCase := isPtr && isOnlyOneFirstField
fieldCode := &StructFieldCode {
typ : fieldType ,
key : tag .Key ,
tag : tag ,
offset : field .Offset ,
isAnonymous : field .Anonymous && !tag .IsTaggedKey && toElemType (fieldType ).Kind () == reflect .Struct ,
isTaggedKey : tag .IsTaggedKey ,
isNilableType : c .isNilableType (fieldType ),
isNilCheck : true ,
}
switch {
case c .isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase (fieldType , isIndirectSpecialCase ):
code , err := c .marshalJSONCode (fieldType )
if err != nil {
return nil , err
}
fieldCode .value = code
fieldCode .isAddrForMarshaler = true
fieldCode .isNilCheck = false
structCode .isIndirect = false
structCode .disableIndirectConversion = true
case c .isMovePointerPositionFromHeadToFirstMarshalTextFieldCase (fieldType , isIndirectSpecialCase ):
code , err := c .marshalTextCode (fieldType )
if err != nil {
return nil , err
}
fieldCode .value = code
fieldCode .isAddrForMarshaler = true
fieldCode .isNilCheck = false
structCode .isIndirect = false
structCode .disableIndirectConversion = true
case isPtr && c .isPtrMarshalJSONType (fieldType ):
code , err := c .marshalJSONCode (fieldType )
if err != nil {
return nil , err
}
fieldCode .value = code
fieldCode .isAddrForMarshaler = true
fieldCode .isNilCheck = false
case isPtr && c .isPtrMarshalTextType (fieldType ):
code , err := c .marshalTextCode (fieldType )
if err != nil {
return nil , err
}
fieldCode .value = code
fieldCode .isAddrForMarshaler = true
fieldCode .isNilCheck = false
default :
code , err := c .typeToCodeWithPtr (fieldType , isPtr )
if err != nil {
return nil , err
}
switch code .Kind () {
case CodeKindPtr , CodeKindInterface :
fieldCode .isNextOpPtrType = true
}
fieldCode .value = code
}
return fieldCode , nil
}
func (c *Compiler ) isAssignableIndirect (fieldCode *StructFieldCode , isPtr bool ) bool {
if isPtr {
return false
}
codeType := fieldCode .value .Kind ()
if codeType == CodeKindMarshalJSON {
return false
}
if codeType == CodeKindMarshalText {
return false
}
return true
}
func (c *Compiler ) getFieldMap (fields []*StructFieldCode ) map [string ][]*StructFieldCode {
fieldMap := map [string ][]*StructFieldCode {}
for _ , field := range fields {
if field .isAnonymous {
for k , v := range c .getAnonymousFieldMap (field ) {
fieldMap [k ] = append (fieldMap [k ], v ...)
}
continue
}
fieldMap [field .key ] = append (fieldMap [field .key ], field )
}
return fieldMap
}
func (c *Compiler ) 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 c .getFieldMapFromAnonymousParent (structCode .fields ) {
fieldMap [k ] = append (fieldMap [k ], v ...)
}
return fieldMap
}
func (c *Compiler ) getFieldMapFromAnonymousParent (fields []*StructFieldCode ) map [string ][]*StructFieldCode {
fieldMap := map [string ][]*StructFieldCode {}
for _ , field := range fields {
if field .isAnonymous {
for k , v := range c .getAnonymousFieldMap (field ) {
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 (c *Compiler ) getDuplicatedFieldMap (fieldMap map [string ][]*StructFieldCode ) map [*StructFieldCode ]struct {} {
duplicatedFieldMap := map [*StructFieldCode ]struct {}{}
for _ , fields := range fieldMap {
if len (fields ) == 1 {
continue
}
if c .isTaggedKeyOnly (fields ) {
for _ , field := range fields {
if field .isTaggedKey {
continue
}
duplicatedFieldMap [field ] = struct {}{}
}
} else {
for _ , field := range fields {
duplicatedFieldMap [field ] = struct {}{}
}
}
}
return duplicatedFieldMap
}
func (c *Compiler ) 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 = c .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 (c *Compiler ) isTaggedKeyOnly (fields []*StructFieldCode ) bool {
var taggedKeyFieldCount int
for _ , field := range fields {
if field .isTaggedKey {
taggedKeyFieldCount ++
}
}
return taggedKeyFieldCount == 1
}
func (c *Compiler ) 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
}
func (c *Compiler ) isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase (typ *runtime .Type , isIndirectSpecialCase bool ) bool {
return isIndirectSpecialCase && !c .isNilableType (typ ) && c .isPtrMarshalJSONType (typ )
}
func (c *Compiler ) isMovePointerPositionFromHeadToFirstMarshalTextFieldCase (typ *runtime .Type , isIndirectSpecialCase bool ) bool {
return isIndirectSpecialCase && !c .isNilableType (typ ) && c .isPtrMarshalTextType (typ )
}
func (c *Compiler ) implementsMarshalJSON (typ *runtime .Type ) bool {
if !c .implementsMarshalJSONType (typ ) {
return false
}
if typ .Kind () != reflect .Ptr {
return true
}
if !c .implementsMarshalJSONType (typ .Elem ()) {
return true
}
return false
}
func (c *Compiler ) implementsMarshalText (typ *runtime .Type ) bool {
if !typ .Implements (marshalTextType ) {
return false
}
if typ .Kind () != reflect .Ptr {
return true
}
if !typ .Elem ().Implements (marshalTextType ) {
return true
}
return false
}
func (c *Compiler ) isNilableType (typ *runtime .Type ) bool {
if !runtime .IfaceIndir (typ ) {
return true
}
switch typ .Kind () {
case reflect .Ptr :
return true
case reflect .Map :
return true
case reflect .Func :
return true
default :
return false
}
}
func (c *Compiler ) implementsMarshalJSONType (typ *runtime .Type ) bool {
return typ .Implements (marshalJSONType ) || typ .Implements (marshalJSONContextType )
}
func (c *Compiler ) isPtrMarshalJSONType (typ *runtime .Type ) bool {
return !c .implementsMarshalJSONType (typ ) && c .implementsMarshalJSONType (runtime .PtrTo (typ ))
}
func (c *Compiler ) isPtrMarshalTextType (typ *runtime .Type ) bool {
return !typ .Implements (marshalTextType ) && runtime .PtrTo (typ ).Implements (marshalTextType )
}
func (c *Compiler ) codeToOpcode (ctx *compileContext , typ *runtime .Type , code Code ) *Opcode {
codes := code .ToOpcode (ctx )
codes .Last ().Next = newEndOp (ctx , typ )
c .linkRecursiveCode (ctx )
return codes .First ()
}
func (c *Compiler ) linkRecursiveCode (ctx *compileContext ) {
recursiveCodes := map [uintptr ]*CompiledCode {}
for _ , recursive := range *ctx .recursiveCodes {
typeptr := uintptr (unsafe .Pointer (recursive .Type ))
codes := ctx .structTypeToCodes [typeptr ]
if recursiveCode , ok := recursiveCodes [typeptr ]; ok {
*recursive .Jmp = *recursiveCode
continue
}
code := copyOpcode (codes .First ())
code .Op = code .Op .PtrHeadToHead ()
lastCode := newEndOp (&compileContext {}, recursive .Type )
lastCode .Op = OpRecursiveEnd
code .End .Next = lastCode
totalLength := code .TotalLength ()
lastCode .Idx = uint32 ((totalLength + 1 ) * uintptrSize )
lastCode .ElemIdx = lastCode .Idx + uintptrSize
lastCode .Length = lastCode .Idx + 2 *uintptrSize
curTotalLength := uintptr (recursive .TotalLength ()) + 3
nextTotalLength := uintptr (totalLength ) + 3
compiled := recursive .Jmp
compiled .Code = code
compiled .CurLen = curTotalLength
compiled .NextLen = nextTotalLength
compiled .Linked = true
recursiveCodes [typeptr ] = compiled
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .