go-json/internal/encoder/code.go

691 lines
17 KiB
Go

package encoder
import (
"reflect"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type Code interface {
Type() CodeType2
ToOpcode() []*Opcode
}
type CodeType2 int
const (
CodeTypeInterface CodeType2 = iota
CodeTypePtr
CodeTypeInt
CodeTypeUint
CodeTypeFloat
CodeTypeString
CodeTypeBool
CodeTypeStruct
CodeTypeMap
CodeTypeSlice
CodeTypeArray
CodeTypeBytes
CodeTypeMarshalJSON
CodeTypeMarshalText
)
type IntCode struct {
typ *runtime.Type
bitSize uint8
isString bool
isPtr bool
}
func (c *IntCode) Type() CodeType2 {
return CodeTypeInt
}
func (c *IntCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type UintCode struct {
typ *runtime.Type
bitSize uint8
isString bool
isPtr bool
}
func (c *UintCode) Type() CodeType2 {
return CodeTypeUint
}
func (c *UintCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type FloatCode struct {
typ *runtime.Type
bitSize uint8
isString bool
isPtr bool
}
func (c *FloatCode) Type() CodeType2 {
return CodeTypeFloat
}
func (c *FloatCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type StringCode struct {
typ *runtime.Type
isString bool
isPtr bool
}
func (c *StringCode) Type() CodeType2 {
return CodeTypeString
}
func (c *StringCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type BoolCode struct {
typ *runtime.Type
isString bool
isPtr bool
}
func (c *BoolCode) Type() CodeType2 {
return CodeTypeBool
}
func (c *BoolCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type SliceCode struct {
typ *runtime.Type
}
func (c *SliceCode) Type() CodeType2 {
return CodeTypeSlice
}
func (c *SliceCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type ArrayCode struct {
typ *runtime.Type
}
func (c *ArrayCode) Type() CodeType2 {
return CodeTypeArray
}
func (c *ArrayCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type MapCode struct {
typ *runtime.Type
}
func (c *MapCode) Type() CodeType2 {
return CodeTypeMap
}
func (c *MapCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type BytesCode struct {
typ *runtime.Type
isPtr bool
}
func (c *BytesCode) Type() CodeType2 {
return CodeTypeBytes
}
func (c *BytesCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type StructCode struct {
typ *runtime.Type
isPtr bool
fields []*StructFieldCode
disableIndirectConversion bool
}
func (c *StructCode) Type() CodeType2 {
return CodeTypeStruct
}
func (c *StructCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) {
fields := make([]*StructFieldCode, 0, len(c.fields))
for _, field := range c.fields {
if field.isAnonymous {
structCode := field.getAnonymousStruct()
if structCode != nil {
structCode.removeFieldsByTags(tags)
if len(structCode.fields) > 0 {
fields = append(fields, field)
}
continue
}
}
if tags.ExistsKey(field.key) {
continue
}
fields = append(fields, field)
}
c.fields = fields
}
type StructFieldCode struct {
typ *runtime.Type
key string
value Code
offset uintptr
isAnonymous bool
isTaggedKey bool
isNilableType bool
isNilCheck bool
isAddrForMarshaler bool
isNextOpPtrType bool
}
func (c *StructFieldCode) getAnonymousStruct() *StructCode {
if !c.isAnonymous {
return nil
}
code, ok := c.value.(*StructCode)
if ok {
return code
}
return nil
}
type InterfaceCode struct {
typ *runtime.Type
isPtr bool
}
func (c *InterfaceCode) Type() CodeType2 {
return CodeTypeInterface
}
func (c *InterfaceCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type MarshalJSONCode struct {
typ *runtime.Type
}
func (c *MarshalJSONCode) Type() CodeType2 {
return CodeTypeMarshalJSON
}
func (c *MarshalJSONCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type MarshalTextCode struct {
typ *runtime.Type
}
func (c *MarshalTextCode) Type() CodeType2 {
return CodeTypeMarshalText
}
func (c *MarshalTextCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
type PtrCode struct {
typ *runtime.Type
value Code
}
func (c *PtrCode) Type() CodeType2 {
return CodeTypePtr
}
func (c *PtrCode) ToOpcode() []*Opcode {
return []*Opcode{}
}
func type2code(ctx *compileContext) (Code, error) {
typ := ctx.typ
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON2(ctx)
case implementsMarshalText(typ):
return compileMarshalText2(ctx)
}
isPtr := false
orgType := typ
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
isPtr = true
}
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON2(ctx)
case implementsMarshalText(typ):
return compileMarshalText2(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 compileBytes2(ctx, isPtr)
}
}
return compileSlice2(ctx)
case reflect.Map:
if isPtr {
return compilePtr2(ctx.withType(runtime.PtrTo(typ)))
}
return compileMap2(ctx.withType(typ))
case reflect.Struct:
return compileStruct2(ctx.withType(typ), isPtr)
case reflect.Int:
return compileInt2(ctx.withType(typ), isPtr)
case reflect.Int8:
return compileInt82(ctx.withType(typ), isPtr)
case reflect.Int16:
return compileInt162(ctx.withType(typ), isPtr)
case reflect.Int32:
return compileInt322(ctx.withType(typ), isPtr)
case reflect.Int64:
return compileInt642(ctx.withType(typ), isPtr)
case reflect.Uint, reflect.Uintptr:
return compileUint2(ctx.withType(typ), isPtr)
case reflect.Uint8:
return compileUint82(ctx.withType(typ), isPtr)
case reflect.Uint16:
return compileUint162(ctx.withType(typ), isPtr)
case reflect.Uint32:
return compileUint322(ctx.withType(typ), isPtr)
case reflect.Uint64:
return compileUint642(ctx.withType(typ), isPtr)
case reflect.Float32:
return compileFloat322(ctx.withType(typ), isPtr)
case reflect.Float64:
return compileFloat642(ctx.withType(typ), isPtr)
case reflect.String:
return compileString2(ctx.withType(typ), isPtr)
case reflect.Bool:
return compileBool2(ctx.withType(typ), isPtr)
case reflect.Interface:
return compileInterface2(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 compileMarshalJSON2(ctx)
case implementsMarshalText(typ):
return compileMarshalText2(ctx)
}
switch typ.Kind() {
case reflect.Ptr:
return compilePtr2(ctx)
case reflect.Slice:
elem := typ.Elem()
if elem.Kind() == reflect.Uint8 {
p := runtime.PtrTo(elem)
if !implementsMarshalJSONType(p) && !p.Implements(marshalTextType) {
return compileBytes2(ctx, false)
}
}
return compileSlice2(ctx)
case reflect.Array:
return compileArray2(ctx)
case reflect.Map:
return compileMap2(ctx)
case reflect.Struct:
return compileStruct2(ctx, isPtr)
case reflect.Interface:
return compileInterface2(ctx, false)
case reflect.Int:
return compileInt2(ctx, false)
case reflect.Int8:
return compileInt82(ctx, false)
case reflect.Int16:
return compileInt162(ctx, false)
case reflect.Int32:
return compileInt322(ctx, false)
case reflect.Int64:
return compileInt642(ctx, false)
case reflect.Uint:
return compileUint2(ctx, false)
case reflect.Uint8:
return compileUint82(ctx, false)
case reflect.Uint16:
return compileUint162(ctx, false)
case reflect.Uint32:
return compileUint322(ctx, false)
case reflect.Uint64:
return compileUint642(ctx, false)
case reflect.Uintptr:
return compileUint2(ctx, false)
case reflect.Float32:
return compileFloat322(ctx, false)
case reflect.Float64:
return compileFloat642(ctx, false)
case reflect.String:
return compileString2(ctx, false)
case reflect.Bool:
return compileBool2(ctx, false)
}
return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)}
}
func compileInt2(ctx *compileContext, isPtr bool) (*IntCode, error) {
return &IntCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
}
func compileInt82(ctx *compileContext, isPtr bool) (*IntCode, error) {
return &IntCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
}
func compileInt162(ctx *compileContext, isPtr bool) (*IntCode, error) {
return &IntCode{typ: ctx.typ, bitSize: 8, isPtr: isPtr}, nil
}
func compileInt322(ctx *compileContext, isPtr bool) (*IntCode, error) {
return &IntCode{typ: ctx.typ, bitSize: 16, isPtr: isPtr}, nil
}
func compileInt642(ctx *compileContext, isPtr bool) (*IntCode, error) {
return &IntCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
}
func compileUint2(ctx *compileContext, isPtr bool) (*UintCode, error) {
return &UintCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
}
func compileUint82(ctx *compileContext, isPtr bool) (*UintCode, error) {
return &UintCode{typ: ctx.typ, bitSize: intSize, isPtr: isPtr}, nil
}
func compileUint162(ctx *compileContext, isPtr bool) (*UintCode, error) {
return &UintCode{typ: ctx.typ, bitSize: 8, isPtr: isPtr}, nil
}
func compileUint322(ctx *compileContext, isPtr bool) (*UintCode, error) {
return &UintCode{typ: ctx.typ, bitSize: 16, isPtr: isPtr}, nil
}
func compileUint642(ctx *compileContext, isPtr bool) (*UintCode, error) {
return &UintCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
}
func compileFloat322(ctx *compileContext, isPtr bool) (*FloatCode, error) {
return &FloatCode{typ: ctx.typ, bitSize: 32, isPtr: isPtr}, nil
}
func compileFloat642(ctx *compileContext, isPtr bool) (*FloatCode, error) {
return &FloatCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil
}
func compileString2(ctx *compileContext, isString bool) (*StringCode, error) {
return &StringCode{typ: ctx.typ, isString: isString}, nil
}
func compileBool2(ctx *compileContext, isString bool) (*BoolCode, error) {
return &BoolCode{typ: ctx.typ, isString: isString}, nil
}
func compileSlice2(ctx *compileContext) (*SliceCode, error) {
return &SliceCode{typ: ctx.typ}, nil
}
func compileArray2(ctx *compileContext) (*ArrayCode, error) {
return &ArrayCode{typ: ctx.typ}, nil
}
func compileMap2(ctx *compileContext) (*MapCode, error) {
return &MapCode{typ: ctx.typ}, nil
}
func compileBytes2(ctx *compileContext, isPtr bool) (*BytesCode, error) {
return &BytesCode{typ: ctx.typ, isPtr: isPtr}, nil
}
func compileInterface2(ctx *compileContext, isPtr bool) (*InterfaceCode, error) {
return &InterfaceCode{typ: ctx.typ, isPtr: isPtr}, nil
}
func compileMarshalJSON2(ctx *compileContext) (*MarshalJSONCode, error) {
return &MarshalJSONCode{typ: ctx.typ}, nil
}
func compileMarshalText2(ctx *compileContext) (*MarshalTextCode, error) {
return &MarshalTextCode{typ: ctx.typ}, nil
}
func compilePtr2(ctx *compileContext) (*PtrCode, error) {
code, err := type2codeWithPtr(ctx.withType(ctx.typ.Elem()), true)
if err != nil {
return nil, err
}
return &PtrCode{typ: ctx.typ, value: code}, nil
}
func compileStruct2(ctx *compileContext, isPtr bool) (*StructCode, error) {
//typeptr := uintptr(unsafe.Pointer(typ))
//compiled := &CompiledCode{}
//ctx.structTypeToCompiledCode[typeptr] = compiled
// header => code => structField => code => end
// ^ |
// |__________|
typ := ctx.typ
fieldNum := typ.NumField()
//indirect := runtime.IfaceIndir(typ)
tags := typeToStructTags(typ)
code := &StructCode{typ: typ, isPtr: isPtr}
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)
}
}
fields = append(fields, field)
}
fieldMap := getFieldMap(fields)
duplicatedFieldMap := getDuplicatedFieldMap(fieldMap)
code.fields = filteredDuplicatedFields(fields, duplicatedFieldMap)
return code, nil
}
func getFieldMap(fields []*StructFieldCode) map[string][]*StructFieldCode {
fieldMap := map[string][]*StructFieldCode{}
for _, field := range fields {
if field.isAnonymous {
structCode := field.getAnonymousStruct()
if structCode != nil {
for k, v := range getFieldMap(structCode.fields) {
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.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)
}
func (c *StructCode) compileStructField(ctx *compileContext, 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,
offset: field.Offset,
isAnonymous: field.Anonymous && !tag.IsTaggedKey,
isTaggedKey: tag.IsTaggedKey,
isNilableType: isNilableType(fieldType),
isNilCheck: true,
}
switch {
case isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(fieldType, isIndirectSpecialCase):
code, err := compileMarshalJSON2(ctx.withType(fieldType))
if err != nil {
return nil, err
}
fieldCode.value = code
fieldCode.isAddrForMarshaler = true
fieldCode.isNilCheck = false
c.disableIndirectConversion = true
case isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(fieldType, isIndirectSpecialCase):
code, err := compileMarshalText2(ctx.withType(fieldType))
if err != nil {
return nil, err
}
fieldCode.value = code
fieldCode.isAddrForMarshaler = true
fieldCode.isNilCheck = false
c.disableIndirectConversion = true
case isPtr && isPtrMarshalJSONType(fieldType):
// *struct{ field T }
// func (*T) MarshalJSON() ([]byte, error)
code, err := compileMarshalJSON2(ctx.withType(fieldType))
if err != nil {
return nil, err
}
fieldCode.value = code
fieldCode.isAddrForMarshaler = true
fieldCode.isNilCheck = false
case isPtr && isPtrMarshalTextType(fieldType):
// *struct{ field T }
// func (*T) MarshalText() ([]byte, error)
code, err := compileMarshalText2(ctx.withType(fieldType))
if err != nil {
return nil, err
}
fieldCode.value = code
fieldCode.isAddrForMarshaler = true
fieldCode.isNilCheck = false
default:
code, err := type2codeWithPtr(ctx.withType(fieldType), isPtr)
if err != nil {
return nil, err
}
switch code.Type() {
case CodeTypePtr, CodeTypeInterface:
fieldCode.isNextOpPtrType = true
}
fieldCode.value = code
fieldCode.isNilCheck = false
}
return fieldCode, nil
}