Merge pull request #148 from goccy/refactor

Fix any bugs of encoder
This commit is contained in:
Masaaki Goshima 2021-03-12 02:14:40 +09:00 committed by GitHub
commit 3ee1ab2711
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 23318 additions and 15739 deletions

View File

@ -1,12 +1,20 @@
name: Go name: Go
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build:
name: Build on limited environment
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: build
run: docker-compose run go-json
test: test:
name: Test name: Test
strategy: strategy:
matrix: matrix:
os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
go-version: [ "1.13", "1.14", "1.15" ] go-version: [ "1.13", "1.14", "1.15", "1.16" ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: setup Go ${{ matrix.go-version }} - name: setup Go ${{ matrix.go-version }}
@ -30,7 +38,7 @@ jobs:
- name: setup Go - name: setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.15 go-version: 1.16
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: measure coverage - name: measure coverage

View File

@ -15,11 +15,8 @@ type opType struct {
Op string Op string
Code string Code string
HeadToPtrHead func() string HeadToPtrHead func() string
HeadToNPtrHead func() string
HeadToAnonymousHead func() string
HeadToOmitEmptyHead func() string HeadToOmitEmptyHead func() string
HeadToStringTagHead func() string HeadToStringTagHead func() string
HeadToOnlyHead func() string
PtrHeadToHead func() string PtrHeadToHead func() string
FieldToEnd func() string FieldToEnd func() string
FieldToOmitEmptyField func() string FieldToOmitEmptyField func() string
@ -30,14 +27,6 @@ func (t opType) IsHeadToPtrHead() bool {
return t.Op != t.HeadToPtrHead() return t.Op != t.HeadToPtrHead()
} }
func (t opType) IsHeadToNPtrHead() bool {
return t.Op != t.HeadToNPtrHead()
}
func (t opType) IsHeadToAnonymousHead() bool {
return t.Op != t.HeadToAnonymousHead()
}
func (t opType) IsHeadToOmitEmptyHead() bool { func (t opType) IsHeadToOmitEmptyHead() bool {
return t.Op != t.HeadToOmitEmptyHead() return t.Op != t.HeadToOmitEmptyHead()
} }
@ -50,10 +39,6 @@ func (t opType) IsPtrHeadToHead() bool {
return t.Op != t.PtrHeadToHead() return t.Op != t.PtrHeadToHead()
} }
func (t opType) IsHeadToOnlyHead() bool {
return t.Op != t.HeadToOnlyHead()
}
func (t opType) IsFieldToEnd() bool { func (t opType) IsFieldToEnd() bool {
return t.Op != t.FieldToEnd() return t.Op != t.FieldToEnd()
} }
@ -71,11 +56,8 @@ func createOpType(op, code string) opType {
Op: op, Op: op,
Code: code, Code: code,
HeadToPtrHead: func() string { return op }, HeadToPtrHead: func() string { return op },
HeadToNPtrHead: func() string { return op },
HeadToAnonymousHead: func() string { return op },
HeadToOmitEmptyHead: func() string { return op }, HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op }, HeadToStringTagHead: func() string { return op },
HeadToOnlyHead: func() string { return op },
PtrHeadToHead: func() string { return op }, PtrHeadToHead: func() string { return op },
FieldToEnd: func() string { return op }, FieldToEnd: func() string { return op },
FieldToOmitEmptyField: func() string { return op }, FieldToOmitEmptyField: func() string { return op },
@ -127,19 +109,19 @@ func (t opType) codeType() codeType {
} }
return codeStructField return codeStructField
} }
if strings.Contains(t.String(), "ArrayHead") { if t.String() == "Array" || t.String() == "ArrayPtr" {
return codeArrayHead return codeArrayHead
} }
if strings.Contains(t.String(), "ArrayElem") { if strings.Contains(t.String(), "ArrayElem") {
return codeArrayElem return codeArrayElem
} }
if strings.Contains(t.String(), "SliceHead") { if t.String() == "Slice" || t.String() == "SlicePtr" {
return codeSliceHead return codeSliceHead
} }
if strings.Contains(t.String(), "SliceElem") { if strings.Contains(t.String(), "SliceElem") {
return codeSliceElem return codeSliceElem
} }
if strings.Contains(t.String(), "MapHead") { if t.String() == "Map" || t.String() == "MapPtr" {
return codeMapHead return codeMapHead
} }
if strings.Contains(t.String(), "MapKey") { if strings.Contains(t.String(), "MapKey") {
@ -159,9 +141,6 @@ func (t opType) headToPtrHead() opType {
if strings.Index(t.String(), "PtrHead") > 0 { if strings.Index(t.String(), "PtrHead") > 0 {
return t return t
} }
if strings.Index(t.String(), "PtrAnonymousHead") > 0 {
return t
}
idx := strings.Index(t.String(), "Field") idx := strings.Index(t.String(), "Field")
if idx == -1 { if idx == -1 {
@ -169,44 +148,15 @@ func (t opType) headToPtrHead() opType {
} }
suffix := "Ptr"+t.String()[idx+len("Field"):] suffix := "Ptr"+t.String()[idx+len("Field"):]
const toPtrOffset = 12 const toPtrOffset = 3
if strings.Contains(opType(int(t) + toPtrOffset).String(), suffix) { if strings.Contains(opType(int(t) + toPtrOffset).String(), suffix) {
return opType(int(t) + toPtrOffset) return opType(int(t) + toPtrOffset)
} }
return t return t
} }
func (t opType) headToNPtrHead() opType {
if strings.Index(t.String(), "PtrHead") > 0 {
return t
}
if strings.Index(t.String(), "PtrAnonymousHead") > 0 {
return t
}
idx := strings.Index(t.String(), "Field")
if idx == -1 {
return t
}
suffix := "NPtr"+t.String()[idx+len("Field"):]
const toPtrOffset = 24
if strings.Contains(opType(int(t) + toPtrOffset).String(), suffix) {
return opType(int(t) + toPtrOffset)
}
return t
}
func (t opType) headToAnonymousHead() opType {
const toAnonymousOffset = 6
if strings.Contains(opType(int(t) + toAnonymousOffset).String(), "Anonymous") {
return opType(int(t) + toAnonymousOffset)
}
return t
}
func (t opType) headToOmitEmptyHead() opType { func (t opType) headToOmitEmptyHead() opType {
const toOmitEmptyOffset = 2 const toOmitEmptyOffset = 1
if strings.Contains(opType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") { if strings.Contains(opType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") {
return opType(int(t) + toOmitEmptyOffset) return opType(int(t) + toOmitEmptyOffset)
} }
@ -215,25 +165,13 @@ func (t opType) headToOmitEmptyHead() opType {
} }
func (t opType) headToStringTagHead() opType { func (t opType) headToStringTagHead() opType {
const toStringTagOffset = 4 const toStringTagOffset = 2
if strings.Contains(opType(int(t) + toStringTagOffset).String(), "StringTag") { if strings.Contains(opType(int(t) + toStringTagOffset).String(), "StringTag") {
return opType(int(t) + toStringTagOffset) return opType(int(t) + toStringTagOffset)
} }
return t return t
} }
func (t opType) headToOnlyHead() opType {
if strings.HasSuffix(t.String(), "Head") || strings.HasSuffix(t.String(), "HeadOmitEmpty") || strings.HasSuffix(t.String(), "HeadStringTag") {
return t
}
const toOnlyOffset = 1
if opType(int(t) + toOnlyOffset).String() == t.String() + "Only" {
return opType(int(t) + toOnlyOffset)
}
return t
}
func (t opType) ptrHeadToHead() opType { func (t opType) ptrHeadToHead() opType {
idx := strings.Index(t.String(), "Ptr") idx := strings.Index(t.String(), "Ptr")
if idx == -1 { if idx == -1 {
@ -241,7 +179,7 @@ func (t opType) ptrHeadToHead() opType {
} }
suffix := t.String()[idx+len("Ptr"):] suffix := t.String()[idx+len("Ptr"):]
const toPtrOffset = 12 const toPtrOffset = 3
if strings.Contains(opType(int(t) - toPtrOffset).String(), suffix) { if strings.Contains(opType(int(t) - toPtrOffset).String(), suffix) {
return opType(int(t) - toPtrOffset) return opType(int(t) - toPtrOffset)
} }
@ -295,11 +233,11 @@ func (t opType) fieldToStringTagField() opType {
"StructEnd", "StructEnd",
} }
primitiveTypes := []string{ primitiveTypes := []string{
"int", "uint", "float32", "float64", "bool", "string", "bytes", "int", "uint", "float32", "float64", "bool", "string", "bytes", "number",
"array", "map", "mapLoad", "slice", "struct", "MarshalJSON", "MarshalText", "recursive", "array", "map", "slice", "struct", "MarshalJSON", "MarshalText", "recursive",
"intString", "uintString", "intString", "uintString",
"intPtr", "uintPtr", "float32Ptr", "float64Ptr", "boolPtr", "stringPtr", "bytesPtr", "intPtr", "uintPtr", "float32Ptr", "float64Ptr", "boolPtr", "stringPtr", "bytesPtr", "numberPtr",
"intNPtr", "uintNPtr", "float32NPtr", "float64NPtr", "boolNPtr", "stringNPtr", "bytesNPtr", "arrayPtr", "mapPtr", "slicePtr", "marshalJSONPtr", "marshalTextPtr", "interfacePtr", "recursivePtr",
} }
primitiveTypesUpper := []string{} primitiveTypesUpper := []string{}
for _, typ := range primitiveTypes { for _, typ := range primitiveTypes {
@ -309,17 +247,10 @@ func (t opType) fieldToStringTagField() opType {
createOpType("End", "Op"), createOpType("End", "Op"),
createOpType("Interface", "Op"), createOpType("Interface", "Op"),
createOpType("Ptr", "Op"), createOpType("Ptr", "Op"),
createOpType("NPtr", "Op"),
createOpType("SliceHead", "SliceHead"),
createOpType("RootSliceHead", "SliceHead"),
createOpType("SliceElem", "SliceElem"), createOpType("SliceElem", "SliceElem"),
createOpType("RootSliceElem", "SliceElem"),
createOpType("SliceEnd", "Op"), createOpType("SliceEnd", "Op"),
createOpType("ArrayHead", "ArrayHead"),
createOpType("ArrayElem", "ArrayElem"), createOpType("ArrayElem", "ArrayElem"),
createOpType("ArrayEnd", "Op"), createOpType("ArrayEnd", "Op"),
createOpType("MapHead", "MapHead"),
createOpType("MapHeadLoad", "MapHead"),
createOpType("MapKey", "MapKey"), createOpType("MapKey", "MapKey"),
createOpType("MapValue", "MapValue"), createOpType("MapValue", "MapValue"),
createOpType("MapEnd", "Op"), createOpType("MapEnd", "Op"),
@ -331,94 +262,47 @@ func (t opType) fieldToStringTagField() opType {
opTypes = append(opTypes, createOpType(typ, "Op")) opTypes = append(opTypes, createOpType(typ, "Op"))
} }
for _, typ := range append(primitiveTypesUpper, "") { for _, typ := range append(primitiveTypesUpper, "") {
for _, ptrOrNot := range []string{"", "Ptr", "NPtr"} { for _, ptrOrNot := range []string{"", "Ptr"} {
for _, headType := range []string{"", "Anonymous"} {
for _, opt := range []string{"", "OmitEmpty", "StringTag"} { for _, opt := range []string{"", "OmitEmpty", "StringTag"} {
for _, onlyOrNot := range []string{"", "Only"} {
ptrOrNot := ptrOrNot ptrOrNot := ptrOrNot
headType := headType
opt := opt opt := opt
typ := typ typ := typ
onlyOrNot := onlyOrNot
op := fmt.Sprintf( op := fmt.Sprintf(
"StructField%s%sHead%s%s%s", "StructField%sHead%s%s",
ptrOrNot, ptrOrNot,
headType,
opt, opt,
typ, typ,
onlyOrNot,
) )
opTypes = append(opTypes, opType{ opTypes = append(opTypes, opType{
Op: op, Op: op,
Code: "StructField", Code: "StructField",
HeadToPtrHead: func() string { HeadToPtrHead: func() string {
return fmt.Sprintf( return fmt.Sprintf(
"StructFieldPtr%sHead%s%s%s", "StructFieldPtrHead%s%s",
headType,
opt, opt,
typ, typ,
onlyOrNot,
)
},
HeadToNPtrHead: func() string {
return fmt.Sprintf(
"StructFieldNPtr%sHead%s%s%s",
headType,
opt,
typ,
onlyOrNot,
)
},
HeadToAnonymousHead: func() string {
return fmt.Sprintf(
"StructField%sAnonymousHead%s%s%s",
ptrOrNot,
opt,
typ,
onlyOrNot,
) )
}, },
HeadToOmitEmptyHead: func() string { HeadToOmitEmptyHead: func() string {
return fmt.Sprintf( return fmt.Sprintf(
"StructField%s%sHeadOmitEmpty%s%s", "StructField%sHeadOmitEmpty%s",
ptrOrNot, ptrOrNot,
headType,
typ, typ,
onlyOrNot,
) )
}, },
HeadToStringTagHead: func() string { HeadToStringTagHead: func() string {
return fmt.Sprintf( return fmt.Sprintf(
"StructField%s%sHeadStringTag%s%s", "StructField%sHeadStringTag%s",
ptrOrNot, ptrOrNot,
headType,
typ,
onlyOrNot,
)
},
HeadToOnlyHead: func() string {
switch typ {
case "", "Array", "Map", "MapLoad", "Slice",
"Struct", "Recursive", "MarshalJSON", "MarshalText",
"IntString", "UintString":
return op
}
return fmt.Sprintf(
"StructField%s%sHead%s%sOnly",
ptrOrNot,
headType,
opt,
typ, typ,
) )
}, },
PtrHeadToHead: func() string { PtrHeadToHead: func() string {
return fmt.Sprintf( return fmt.Sprintf(
"StructField%sHead%s%s%s", "StructFieldHead%s%s",
headType,
opt, opt,
typ, typ,
onlyOrNot,
) )
}, },
FieldToEnd: func() string { return op }, FieldToEnd: func() string { return op },
@ -428,8 +312,6 @@ func (t opType) fieldToStringTagField() opType {
} }
} }
} }
}
}
for _, typ := range append(primitiveTypesUpper, "") { for _, typ := range append(primitiveTypesUpper, "") {
for _, opt := range []string{"", "OmitEmpty", "StringTag"} { for _, opt := range []string{"", "OmitEmpty", "StringTag"} {
opt := opt opt := opt
@ -444,15 +326,12 @@ func (t opType) fieldToStringTagField() opType {
Op: op, Op: op,
Code: "StructField", Code: "StructField",
HeadToPtrHead: func() string { return op }, HeadToPtrHead: func() string { return op },
HeadToNPtrHead: func() string { return op },
HeadToAnonymousHead: func() string { return op },
HeadToOmitEmptyHead: func() string { return op }, HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op }, HeadToStringTagHead: func() string { return op },
HeadToOnlyHead: func() string { return op },
PtrHeadToHead: func() string { return op }, PtrHeadToHead: func() string { return op },
FieldToEnd: func() string { FieldToEnd: func() string {
switch typ { switch typ {
case "", "Array", "Map", "MapLoad", "Slice", "Struct", "Recursive": case "", "Array", "Map", "Slice", "Struct", "Recursive":
return op return op
} }
return fmt.Sprintf( return fmt.Sprintf(
@ -490,11 +369,8 @@ func (t opType) fieldToStringTagField() opType {
Op: op, Op: op,
Code: "StructEnd", Code: "StructEnd",
HeadToPtrHead: func() string { return op }, HeadToPtrHead: func() string { return op },
HeadToNPtrHead: func() string { return op },
HeadToAnonymousHead: func() string { return op },
HeadToOmitEmptyHead: func() string { return op }, HeadToOmitEmptyHead: func() string { return op },
HeadToStringTagHead: func() string { return op }, HeadToStringTagHead: func() string { return op },
HeadToOnlyHead: func() string { return op },
PtrHeadToHead: func() string { return op }, PtrHeadToHead: func() string { return op },
FieldToEnd: func() string { return op }, FieldToEnd: func() string { return op },
FieldToOmitEmptyField: func() string { return op }, FieldToOmitEmptyField: func() string { return op },

View File

@ -5,6 +5,9 @@ import (
) )
func compact(dst *bytes.Buffer, src []byte, escape bool) error { func compact(dst *bytes.Buffer, src []byte, escape bool) error {
if len(src) == 0 {
return errUnexpectedEndOfJSON("", 0)
}
length := len(src) length := len(src)
for cursor := 0; cursor < length; cursor++ { for cursor := 0; cursor < length; cursor++ {
c := src[cursor] c := src[cursor]

1859
cover_array_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1774,11 +1774,11 @@ func TestCoverBool(t *testing.T) {
enc.SetIndent("", " ") enc.SetIndent("", " ")
} }
if err := enc.Encode(test.data); err != nil { if err := enc.Encode(test.data); err != nil {
t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err)
} }
stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape)
if buf.String() != stdresult { if buf.String() != stdresult {
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
} }
} }
} }

View File

@ -19,6 +19,9 @@ func float32ptr(v float32) *float32 { return &v }
func float64ptr(v float64) *float64 { return &v } func float64ptr(v float64) *float64 { return &v }
func stringptr(v string) *string { return &v } func stringptr(v string) *string { return &v }
func boolptr(v bool) *bool { return &v } func boolptr(v bool) *bool { return &v }
func sliceptr(v []int) *[]int { return &v }
func arrayptr(v [2]int) *[2]int { return &v }
func mapptr(v map[string]int) *map[string]int { return &v }
func encodeByEncodingJSON(data interface{}, indent, escape bool) string { func encodeByEncodingJSON(data interface{}, indent, escape bool) string {
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -408,6 +408,29 @@ func TestCoverInt16(t *testing.T) {
}{A: nil, B: nil}, }{A: nil, B: nil},
}, },
// PtrHeadInt16NilMultiFields
{
name: "PtrHeadInt16NilMultiFields",
data: (*struct {
A int16 `json:"a"`
B int16 `json:"b"`
})(nil),
},
{
name: "PtrHeadInt16NilMultiFieldsOmitEmpty",
data: (*struct {
A int16 `json:"a,omitempty"`
B int16 `json:"b,omitempty"`
})(nil),
},
{
name: "PtrHeadInt16NilMultiFieldsString",
data: (*struct {
A int16 `json:"a,string"`
B int16 `json:"b,string"`
})(nil),
},
// PtrHeadInt16NilMultiFields // PtrHeadInt16NilMultiFields
{ {
name: "PtrHeadInt16NilMultiFields", name: "PtrHeadInt16NilMultiFields",

View File

@ -1774,11 +1774,11 @@ func TestCoverInt(t *testing.T) {
enc.SetIndent("", " ") enc.SetIndent("", " ")
} }
if err := enc.Encode(test.data); err != nil { if err := enc.Encode(test.data); err != nil {
t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err)
} }
stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape)
if buf.String() != stdresult { if buf.String() != stdresult {
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
} }
} }
} }

1859
cover_map_test.go Normal file

File diff suppressed because it is too large Load Diff

3546
cover_marshal_json_test.go Normal file

File diff suppressed because it is too large Load Diff

3546
cover_marshal_text_test.go Normal file

File diff suppressed because it is too large Load Diff

1859
cover_slice_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1793,11 +1793,11 @@ func TestCoverString(t *testing.T) {
enc.SetIndent("", " ") enc.SetIndent("", " ")
} }
if err := enc.Encode(test.data); err != nil { if err := enc.Encode(test.data); err != nil {
t.Fatalf("%s(htmlEscape:%T): %v: %s", test.name, htmlEscape, test.data, err) t.Fatalf("%s(htmlEscape:%v,indent:%v): %v: %s", test.name, htmlEscape, indent, test.data, err)
} }
stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape)
if buf.String() != stdresult { if buf.String() != stdresult {
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
} }
} }
} }

View File

@ -1774,11 +1774,11 @@ func TestCoverUint16(t *testing.T) {
enc.SetIndent("", " ") enc.SetIndent("", " ")
} }
if err := enc.Encode(test.data); err != nil { if err := enc.Encode(test.data); err != nil {
t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err)
} }
stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape)
if buf.String() != stdresult { if buf.String() != stdresult {
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
} }
} }
} }

View File

@ -80,7 +80,7 @@ func decodeCompile(typ *rtype, structName, fieldName string, structTypeToDecoder
case reflect.Uint64: case reflect.Uint64:
return decodeCompileUint64(typ, structName, fieldName) return decodeCompileUint64(typ, structName, fieldName)
case reflect.String: case reflect.String:
return decodeCompileString(structName, fieldName) return decodeCompileString(typ, structName, fieldName)
case reflect.Bool: case reflect.Bool:
return decodeCompileBool(structName, fieldName) return decodeCompileBool(structName, fieldName)
case reflect.Float32: case reflect.Float32:
@ -203,7 +203,12 @@ func decodeCompileFloat64(structName, fieldName string) (decoder, error) {
}), nil }), nil
} }
func decodeCompileString(structName, fieldName string) (decoder, error) { func decodeCompileString(typ *rtype, structName, fieldName string) (decoder, error) {
if typ == type2rtype(jsonNumberType) {
return newNumberDecoder(structName, fieldName, func(p unsafe.Pointer, v Number) {
*(*Number)(p) = v
}), nil
}
return newStringDecoder(structName, fieldName), nil return newStringDecoder(structName, fieldName), nil
} }

View File

@ -1,11 +1,12 @@
package json package json
import ( import (
"strconv"
"unsafe" "unsafe"
) )
type numberDecoder struct { type numberDecoder struct {
*floatDecoder stringDecoder *stringDecoder
op func(unsafe.Pointer, Number) op func(unsafe.Pointer, Number)
structName string structName string
fieldName string fieldName string
@ -13,7 +14,7 @@ type numberDecoder struct {
func newNumberDecoder(structName, fieldName string, op func(unsafe.Pointer, Number)) *numberDecoder { func newNumberDecoder(structName, fieldName string, op func(unsafe.Pointer, Number)) *numberDecoder {
return &numberDecoder{ return &numberDecoder{
floatDecoder: newFloatDecoder(structName, fieldName, nil), stringDecoder: newStringDecoder(structName, fieldName),
op: op, op: op,
structName: structName, structName: structName,
fieldName: fieldName, fieldName: fieldName,
@ -21,22 +22,97 @@ func newNumberDecoder(structName, fieldName string, op func(unsafe.Pointer, Numb
} }
func (d *numberDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer) error { func (d *numberDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer) error {
bytes, err := d.floatDecoder.decodeStreamByte(s) bytes, err := d.decodeStreamByte(s)
if err != nil { if err != nil {
return err return err
} }
if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil {
return &SyntaxError{msg: err.Error(), Offset: s.totalOffset()}
}
d.op(p, Number(string(bytes))) d.op(p, Number(string(bytes)))
s.reset() s.reset()
return nil return nil
} }
func (d *numberDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) { func (d *numberDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
bytes, c, err := d.floatDecoder.decodeByte(buf, cursor) bytes, c, err := d.decodeByte(buf, cursor)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil {
return 0, &SyntaxError{msg: err.Error(), Offset: c}
}
cursor = c cursor = c
s := *(*string)(unsafe.Pointer(&bytes)) s := *(*string)(unsafe.Pointer(&bytes))
d.op(p, Number(s)) d.op(p, Number(s))
return cursor, nil return cursor, nil
} }
func (d *numberDecoder) decodeStreamByte(s *stream) ([]byte, error) {
for {
switch s.char() {
case ' ', '\n', '\t', '\r':
s.cursor++
continue
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return floatBytes(s), nil
case 'n':
if err := nullBytes(s); err != nil {
return nil, err
}
return nil, nil
case '"':
return d.stringDecoder.decodeStreamByte(s)
case nul:
if s.read() {
continue
}
goto ERROR
default:
goto ERROR
}
}
ERROR:
return nil, errUnexpectedEndOfJSON("json.Number", s.totalOffset())
}
func (d *numberDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) {
buflen := int64(len(buf))
for ; cursor < buflen; cursor++ {
switch buf[cursor] {
case ' ', '\n', '\t', '\r':
continue
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
start := cursor
cursor++
for ; cursor < buflen; cursor++ {
if floatTable[buf[cursor]] {
continue
}
break
}
num := buf[start:cursor]
return num, cursor, nil
case 'n':
if cursor+3 >= buflen {
return nil, 0, errUnexpectedEndOfJSON("null", cursor)
}
if buf[cursor+1] != 'u' {
return nil, 0, errInvalidCharacter(buf[cursor+1], "null", cursor)
}
if buf[cursor+2] != 'l' {
return nil, 0, errInvalidCharacter(buf[cursor+2], "null", cursor)
}
if buf[cursor+3] != 'l' {
return nil, 0, errInvalidCharacter(buf[cursor+3], "null", cursor)
}
cursor += 4
return nil, cursor, nil
case '"':
return d.stringDecoder.decodeByte(buf, cursor)
default:
return nil, 0, errUnexpectedEndOfJSON("json.Number", cursor)
}
}
return nil, 0, errUnexpectedEndOfJSON("json.Number", cursor)
}

View File

@ -1251,31 +1251,31 @@ var unmarshalTests = []unmarshalTest{
in: `invalid`, // 143 in: `invalid`, // 143
ptr: new(json.Number), ptr: new(json.Number),
err: json.NewSyntaxError( err: json.NewSyntaxError(
`json: invalid character v as null`, `json: json.Number unexpected end of JSON input`,
1, 1,
), ),
}, },
{ {
in: `"invalid"`, // 144 in: `"invalid"`, // 144
ptr: new(json.Number), ptr: new(json.Number),
err: fmt.Errorf(`strconv.ParseFloat: parsing "\"invalid\"": invalid syntax`), err: fmt.Errorf(`strconv.ParseFloat: parsing "invalid": invalid syntax`),
}, },
{ {
in: `{"A":"invalid"}`, // 145 in: `{"A":"invalid"}`, // 145
ptr: new(struct{ A json.Number }), ptr: new(struct{ A json.Number }),
err: fmt.Errorf(`strconv.ParseFloat: parsing "\"invalid\"": invalid syntax`), err: fmt.Errorf(`strconv.ParseFloat: parsing "invalid": invalid syntax`),
}, },
{ {
in: `{"A":"invalid"}`, // 146 in: `{"A":"invalid"}`, // 146
ptr: new(struct { ptr: new(struct {
A json.Number `json:",string"` A json.Number `json:",string"`
}), }),
err: fmt.Errorf(`json: null unexpected end of JSON input`), err: fmt.Errorf(`json: json.Number unexpected end of JSON input`),
}, },
{ {
in: `{"A":"invalid"}`, // 147 in: `{"A":"invalid"}`, // 147
ptr: new(map[string]json.Number), ptr: new(map[string]json.Number),
err: fmt.Errorf(`strconv.ParseFloat: parsing "\"invalid\"": invalid syntax`), err: fmt.Errorf(`strconv.ParseFloat: parsing "invalid": invalid syntax`),
}, },
/* /*
// invalid UTF-8 is coerced to valid UTF-8. // invalid UTF-8 is coerced to valid UTF-8.

13
docker-compose.yml Normal file
View File

@ -0,0 +1,13 @@
version: '2'
services:
go-json:
image: golang:1.16
volumes:
- '.:/go/src/go-json'
deploy:
resources:
limits:
memory: 2048M
working_dir: /go/src/go-json
command: |
sh -c "go test -c . && ls go-json.test"

144
encode.go
View File

@ -2,10 +2,14 @@ package json
import ( import (
"bytes" "bytes"
"encoding"
"encoding/base64" "encoding/base64"
"fmt"
"io" "io"
"math" "math"
"reflect"
"strconv" "strconv"
"strings"
"sync" "sync"
"unsafe" "unsafe"
) )
@ -358,7 +362,147 @@ func encodeByteSlice(b []byte, src []byte) []byte {
return append(append(b, buf...), '"') return append(append(b, buf...), '"')
} }
func encodeNumber(b []byte, n Number) ([]byte, error) {
if len(n) == 0 {
return append(b, '0'), nil
}
for i := 0; i < len(n); i++ {
if !floatTable[n[i]] {
return nil, fmt.Errorf("json: invalid number literal %q", n)
}
}
b = append(b, n...)
return b, nil
}
func appendIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte { func appendIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.prefix...) b = append(b, ctx.prefix...)
return append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+indent)...) return append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+indent)...)
} }
func encodeIsNilForMarshaler(v interface{}) bool {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Interface, reflect.Map, reflect.Ptr:
return rv.IsNil()
case reflect.Slice:
return rv.IsNil() || rv.Len() == 0
}
return false
}
func encodeMarshalJSON(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(Marshaler)
if !ok {
return encodeNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, escape); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return buf.Bytes(), nil
}
func encodeMarshalJSONIndent(ctx *encodeRuntimeContext, code *opcode, b []byte, v interface{}, indent int, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(Marshaler)
if !ok {
return encodeNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var compactBuf bytes.Buffer
if err := compact(&compactBuf, bb, escape); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var indentBuf bytes.Buffer
if err := encodeWithIndent(
&indentBuf,
compactBuf.Bytes(),
string(ctx.prefix)+strings.Repeat(string(ctx.indentStr), ctx.baseIndent+indent+1),
string(ctx.indentStr),
); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return append(b, indentBuf.Bytes()...), nil
}
func encodeMarshalText(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return encodeNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return encodeEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
func encodeMarshalTextIndent(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return encodeNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return encodeEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}

View File

@ -24,6 +24,7 @@ type opcodeSet struct {
var ( var (
marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem() marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem()
marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
jsonNumberType = reflect.TypeOf(Number(""))
) )
func encodeCompileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) { func encodeCompileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) {
@ -37,7 +38,6 @@ func encodeCompileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) {
code, err := encodeCompileHead(&encodeCompileContext{ code, err := encodeCompileHead(&encodeCompileContext{
typ: copiedType, typ: copiedType,
root: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{}, structTypeToCompiledCode: map[uintptr]*compiledCode{},
}) })
if err != nil { if err != nil {
@ -56,42 +56,44 @@ func encodeCompileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) {
func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) {
typ := ctx.typ typ := ctx.typ
switch { switch {
case typ.Implements(marshalJSONType): case encodeImplementsMarshalJSON(typ):
return encodeCompileMarshalJSON(ctx) return encodeCompileMarshalJSON(ctx)
case rtype_ptrTo(typ).Implements(marshalJSONType): case encodeImplementsMarshalText(typ):
return encodeCompileMarshalJSONPtr(ctx)
case typ.Implements(marshalTextType):
return encodeCompileMarshalText(ctx) return encodeCompileMarshalText(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType):
return encodeCompileMarshalTextPtr(ctx)
} }
isPtr := false isPtr := false
orgType := typ orgType := typ
if typ.Kind() == reflect.Ptr { if typ.Kind() == reflect.Ptr {
typ = typ.Elem() typ = typ.Elem()
isPtr = true isPtr = true
} }
switch {
case encodeImplementsMarshalJSON(typ):
return encodeCompileMarshalJSON(ctx)
case encodeImplementsMarshalText(typ):
return encodeCompileMarshalText(ctx)
}
if typ.Kind() == reflect.Map { if typ.Kind() == reflect.Map {
return encodeCompileMap(ctx.withType(typ), isPtr) if isPtr {
return encodeCompilePtr(ctx.withType(rtype_ptrTo(typ)))
}
return encodeCompileMap(ctx.withType(typ))
} else if typ.Kind() == reflect.Struct { } else if typ.Kind() == reflect.Struct {
code, err := encodeCompileStruct(ctx.withType(typ), isPtr) code, err := encodeCompileStruct(ctx.withType(typ), isPtr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
encodeConvertHeadOnlyCode(code, isPtr)
encodeOptimizeStructEnd(code) encodeOptimizeStructEnd(code)
encodeLinkRecursiveCode(code) encodeLinkRecursiveCode(code)
return code, nil return code, nil
} else if isPtr && typ.Implements(marshalTextType) { } else if isPtr && typ.Implements(marshalTextType) {
typ = orgType typ = orgType
} else if isPtr && typ.Implements(marshalJSONType) {
typ = orgType
} }
code, err := encodeCompile(ctx.withType(typ)) code, err := encodeCompile(ctx.withType(typ), isPtr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
encodeConvertHeadOnlyCode(code, isPtr)
encodeOptimizeStructEnd(code) encodeOptimizeStructEnd(code)
encodeLinkRecursiveCode(code) encodeLinkRecursiveCode(code)
return code, nil return code, nil
@ -100,9 +102,7 @@ func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) {
func encodeLinkRecursiveCode(c *opcode) { func encodeLinkRecursiveCode(c *opcode) {
for code := c; code.op != opEnd && code.op != opStructFieldRecursiveEnd; { for code := c; code.op != opEnd && code.op != opStructFieldRecursiveEnd; {
switch code.op { switch code.op {
case opStructFieldRecursive, case opStructFieldRecursive, opStructFieldRecursivePtr:
opStructFieldPtrAnonymousHeadRecursive,
opStructFieldAnonymousHeadRecursive:
if code.jmp.linked { if code.jmp.linked {
code = code.next code = code.next
continue continue
@ -143,7 +143,7 @@ func encodeLinkRecursiveCode(c *opcode) {
func encodeOptimizeStructEnd(c *opcode) { func encodeOptimizeStructEnd(c *opcode) {
for code := c; code.op != opEnd; { for code := c; code.op != opEnd; {
if code.op == opStructFieldRecursive { if code.op == opStructFieldRecursive || code.op == opStructFieldRecursivePtr {
// ignore if exists recursive operation // ignore if exists recursive operation
return return
} }
@ -163,7 +163,13 @@ func encodeOptimizeStructEnd(c *opcode) {
switch code.op { switch code.op {
case opStructEnd: case opStructEnd:
prev := code.prevField prev := code.prevField
if strings.Contains(prev.op.String(), "Head") { prevOp := prev.op.String()
if strings.Contains(prevOp, "Head") ||
strings.Contains(prevOp, "Slice") ||
strings.Contains(prevOp, "Array") ||
strings.Contains(prevOp, "Map") ||
strings.Contains(prevOp, "MarshalJSON") ||
strings.Contains(prevOp, "MarshalText") {
// not exists field // not exists field
code = code.next code = code.next
break break
@ -182,96 +188,62 @@ func encodeOptimizeStructEnd(c *opcode) {
} }
} }
func encodeConvertHeadOnlyCode(c *opcode, isPtrHead bool) { func encodeImplementsMarshalJSON(typ *rtype) bool {
if c.nextField == nil { if !typ.Implements(marshalJSONType) {
return return false
} }
if c.nextField.op.codeType() != codeStructEnd { if typ.Kind() != reflect.Ptr {
return
}
switch c.op {
case opStructFieldHead:
encodeConvertHeadOnlyCode(c.next, false)
if !strings.Contains(c.next.op.String(), "Only") {
return
}
c.op = opStructFieldHeadOnly
case opStructFieldHeadOmitEmpty:
encodeConvertHeadOnlyCode(c.next, false)
if !strings.Contains(c.next.op.String(), "Only") {
return
}
c.op = opStructFieldHeadOmitEmptyOnly
case opStructFieldHeadStringTag:
encodeConvertHeadOnlyCode(c.next, false)
if !strings.Contains(c.next.op.String(), "Only") {
return
}
c.op = opStructFieldHeadStringTagOnly
case opStructFieldPtrHead:
}
if strings.Contains(c.op.String(), "Marshal") {
return
}
if strings.Contains(c.op.String(), "Slice") {
return
}
if strings.Contains(c.op.String(), "Map") {
return
}
isPtrOp := strings.Contains(c.op.String(), "Ptr")
if isPtrOp && !isPtrHead {
c.op = c.op.headToOnlyHead()
} else if !isPtrOp && isPtrHead {
c.op = c.op.headToPtrHead().headToOnlyHead()
} else if isPtrOp && isPtrHead {
c.op = c.op.headToPtrHead().headToOnlyHead()
}
}
func encodeImplementsMarshaler(typ *rtype) bool {
switch {
case typ.Implements(marshalJSONType):
return true
case rtype_ptrTo(typ).Implements(marshalJSONType):
return true
case typ.Implements(marshalTextType):
return true
case rtype_ptrTo(typ).Implements(marshalTextType):
return true return true
} }
// type kind is reflect.Ptr
if !typ.Elem().Implements(marshalJSONType) {
return true
}
// needs to dereference
return false return false
} }
func encodeCompile(ctx *encodeCompileContext) (*opcode, error) { func encodeImplementsMarshalText(typ *rtype) 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 encodeCompile(ctx *encodeCompileContext, isPtr bool) (*opcode, error) {
typ := ctx.typ typ := ctx.typ
switch { switch {
case typ.Implements(marshalJSONType): case encodeImplementsMarshalJSON(typ):
return encodeCompileMarshalJSON(ctx) return encodeCompileMarshalJSON(ctx)
case rtype_ptrTo(typ).Implements(marshalJSONType): case encodeImplementsMarshalText(typ):
return encodeCompileMarshalJSONPtr(ctx)
case typ.Implements(marshalTextType):
return encodeCompileMarshalText(ctx) return encodeCompileMarshalText(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType):
return encodeCompileMarshalTextPtr(ctx)
} }
switch typ.Kind() { switch typ.Kind() {
case reflect.Ptr: case reflect.Ptr:
return encodeCompilePtr(ctx) return encodeCompilePtr(ctx)
case reflect.Slice: case reflect.Slice:
elem := typ.Elem() elem := typ.Elem()
if !encodeImplementsMarshaler(elem) && elem.Kind() == reflect.Uint8 { if elem.Kind() == reflect.Uint8 {
p := rtype_ptrTo(elem)
if !p.Implements(marshalJSONType) && !p.Implements(marshalTextType) {
return encodeCompileBytes(ctx) return encodeCompileBytes(ctx)
} }
}
return encodeCompileSlice(ctx) return encodeCompileSlice(ctx)
case reflect.Array: case reflect.Array:
return encodeCompileArray(ctx) return encodeCompileArray(ctx)
case reflect.Map: case reflect.Map:
return encodeCompileMap(ctx, true) return encodeCompileMap(ctx)
case reflect.Struct: case reflect.Struct:
return encodeCompileStruct(ctx, false) return encodeCompileStruct(ctx, isPtr)
case reflect.Interface: case reflect.Interface:
return encodeCompileInterface(ctx) return encodeCompileInterface(ctx)
case reflect.Int: case reflect.Int:
@ -308,13 +280,51 @@ func encodeCompile(ctx *encodeCompileContext) (*opcode, error) {
return nil, &UnsupportedTypeError{Type: rtype2type(typ)} return nil, &UnsupportedTypeError{Type: rtype2type(typ)}
} }
func encodeConvertPtrOp(code *opcode) opType {
ptrHeadOp := code.op.headToPtrHead()
if code.op != ptrHeadOp {
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 opArray:
return opArrayPtr
case opSlice:
return opSlicePtr
case opMap:
return opMapPtr
case opMarshalJSON:
return opMarshalJSONPtr
case opMarshalText:
return opMarshalTextPtr
case opInterface:
return opInterfacePtr
case opStructFieldRecursive:
return opStructFieldRecursivePtr
}
return code.op
}
func encodeCompileKey(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileKey(ctx *encodeCompileContext) (*opcode, error) {
typ := ctx.typ typ := ctx.typ
switch { switch {
case rtype_ptrTo(typ).Implements(marshalJSONType): case encodeImplementsMarshalJSON(typ):
return encodeCompileMarshalJSONPtr(ctx) return encodeCompileMarshalJSON(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType): case encodeImplementsMarshalText(typ):
return encodeCompileMarshalTextPtr(ctx) return encodeCompileMarshalText(ctx)
} }
switch typ.Kind() { switch typ.Kind() {
case reflect.Ptr: case reflect.Ptr:
@ -350,46 +360,31 @@ func encodeCompileKey(ctx *encodeCompileContext) (*opcode, error) {
} }
func encodeCompilePtr(ctx *encodeCompileContext) (*opcode, error) { func encodeCompilePtr(ctx *encodeCompileContext) (*opcode, error) {
ptrOpcodeIndex := ctx.opcodeIndex code, err := encodeCompile(ctx.withType(ctx.typ.Elem()), true)
ptrIndex := ctx.ptrIndex
ctx.incIndex()
code, err := encodeCompile(ctx.withType(ctx.typ.Elem()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
ptrHeadOp := code.op.headToPtrHead() code.op = encodeConvertPtrOp(code)
if code.op != ptrHeadOp { code.ptrNum++
code.op = ptrHeadOp
code.decOpcodeIndex()
ctx.decIndex()
return code, nil return code, nil
} }
c := ctx.context()
c.opcodeIndex = ptrOpcodeIndex
c.ptrIndex = ptrIndex
return newOpCodeWithNext(c, opPtr, code), nil
}
func encodeCompileMarshalJSON(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileMarshalJSON(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx, opMarshalJSON) code := newOpCode(ctx, opMarshalJSON)
ctx.incIndex() typ := ctx.typ
return code, nil if !typ.Implements(marshalJSONType) && rtype_ptrTo(typ).Implements(marshalJSONType) {
code.addrForMarshaler = true
} }
func encodeCompileMarshalJSONPtr(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalJSON)
ctx.incIndex() ctx.incIndex()
return code, nil return code, nil
} }
func encodeCompileMarshalText(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileMarshalText(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx, opMarshalText) code := newOpCode(ctx, opMarshalText)
ctx.incIndex() typ := ctx.typ
return code, nil if !typ.Implements(marshalTextType) && rtype_ptrTo(typ).Implements(marshalTextType) {
code.addrForMarshaler = true
} }
func encodeCompileMarshalTextPtr(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalText)
ctx.incIndex() ctx.incIndex()
return code, nil return code, nil
} }
@ -593,7 +588,13 @@ func encodeCompileFloat64(ctx *encodeCompileContext) (*opcode, error) {
} }
func encodeCompileString(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileString(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx, opString) var op opType
if ctx.typ == type2rtype(jsonNumberType) {
op = opNumber
} else {
op = opString
}
code := newOpCode(ctx, op)
ctx.incIndex() ctx.incIndex()
return code, nil return code, nil
} }
@ -617,14 +618,13 @@ func encodeCompileInterface(ctx *encodeCompileContext) (*opcode, error) {
} }
func encodeCompileSlice(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileSlice(ctx *encodeCompileContext) (*opcode, error) {
ctx.root = false
elem := ctx.typ.Elem() elem := ctx.typ.Elem()
size := elem.Size() size := elem.Size()
header := newSliceHeaderCode(ctx) header := newSliceHeaderCode(ctx)
ctx.incIndex() ctx.incIndex()
code, err := encodeCompile(ctx.withType(ctx.typ.Elem()).incIndent()) code, err := encodeCompileSliceElem(ctx.withType(elem).incIndent())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -648,8 +648,19 @@ func encodeCompileSlice(ctx *encodeCompileContext) (*opcode, error) {
return (*opcode)(unsafe.Pointer(header)), nil return (*opcode)(unsafe.Pointer(header)), nil
} }
func encodeCompileSliceElem(ctx *encodeCompileContext) (*opcode, error) {
typ := ctx.typ
switch {
case !typ.Implements(marshalJSONType) && rtype_ptrTo(typ).Implements(marshalJSONType):
return encodeCompileMarshalJSON(ctx)
case !typ.Implements(marshalTextType) && rtype_ptrTo(typ).Implements(marshalTextType):
return encodeCompileMarshalText(ctx)
default:
return encodeCompile(ctx, false)
}
}
func encodeCompileArray(ctx *encodeCompileContext) (*opcode, error) { func encodeCompileArray(ctx *encodeCompileContext) (*opcode, error) {
ctx.root = false
typ := ctx.typ typ := ctx.typ
elem := typ.Elem() elem := typ.Elem()
alen := typ.Len() alen := typ.Len()
@ -658,7 +669,7 @@ func encodeCompileArray(ctx *encodeCompileContext) (*opcode, error) {
header := newArrayHeaderCode(ctx, alen) header := newArrayHeaderCode(ctx, alen)
ctx.incIndex() ctx.incIndex()
code, err := encodeCompile(ctx.withType(elem).incIndent()) code, err := encodeCompile(ctx.withType(elem).incIndent(), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -697,12 +708,12 @@ func mapiternext(it unsafe.Pointer)
//go:noescape //go:noescape
func maplen(m unsafe.Pointer) int func maplen(m unsafe.Pointer) int
func encodeCompileMap(ctx *encodeCompileContext, withLoad bool) (*opcode, error) { func encodeCompileMap(ctx *encodeCompileContext) (*opcode, error) {
// header => code => value => code => key => code => value => code => end // header => code => value => code => key => code => value => code => end
// ^ | // ^ |
// |_______________________| // |_______________________|
ctx = ctx.incIndent() ctx = ctx.incIndent()
header := newMapHeaderCode(ctx, withLoad) header := newMapHeaderCode(ctx)
ctx.incIndex() ctx.incIndex()
typ := ctx.typ typ := ctx.typ
@ -716,7 +727,7 @@ func encodeCompileMap(ctx *encodeCompileContext, withLoad bool) (*opcode, error)
ctx.incIndex() ctx.incIndex()
valueType := typ.Elem() valueType := typ.Elem()
valueCode, err := encodeCompile(ctx.withType(valueType)) valueCode, err := encodeCompile(ctx.withType(valueType), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -745,174 +756,122 @@ func encodeCompileMap(ctx *encodeCompileContext, withLoad bool) (*opcode, error)
return (*opcode)(unsafe.Pointer(header)), nil return (*opcode)(unsafe.Pointer(header)), nil
} }
func encodeTypeToHeaderType(ctx *encodeCompileContext, code *opcode) opType { func encodeTypeToHeaderType(code *opcode) opType {
switch code.op { switch code.op {
case opPtr:
ptrNum := 1
c := code
ctx.decIndex()
for {
if code.next.op == opPtr {
ptrNum++
code = code.next
ctx.decIndex()
continue
}
break
}
c.ptrNum = ptrNum
if ptrNum > 1 {
switch code.next.op {
case opInt:
c.mask = code.next.mask
c.rshiftNum = code.next.rshiftNum
return opStructFieldHeadIntNPtr
case opUint:
c.mask = code.next.mask
return opStructFieldHeadUintNPtr
case opFloat32:
return opStructFieldHeadFloat32NPtr
case opFloat64:
return opStructFieldHeadFloat64NPtr
case opString:
return opStructFieldHeadStringNPtr
case opBool:
return opStructFieldHeadBoolNPtr
}
} else {
switch code.next.op {
case opInt:
c.mask = code.next.mask
c.rshiftNum = code.next.rshiftNum
return opStructFieldHeadIntPtr
case opUint:
c.mask = code.next.mask
return opStructFieldHeadUintPtr
case opFloat32:
return opStructFieldHeadFloat32Ptr
case opFloat64:
return opStructFieldHeadFloat64Ptr
case opString:
return opStructFieldHeadStringPtr
case opBool:
return opStructFieldHeadBoolPtr
}
}
case opInt: case opInt:
return opStructFieldHeadInt return opStructFieldHeadInt
case opIntPtr:
return opStructFieldHeadIntPtr
case opUint: case opUint:
return opStructFieldHeadUint return opStructFieldHeadUint
case opUintPtr:
return opStructFieldHeadUintPtr
case opFloat32: case opFloat32:
return opStructFieldHeadFloat32 return opStructFieldHeadFloat32
case opFloat32Ptr:
return opStructFieldHeadFloat32Ptr
case opFloat64: case opFloat64:
return opStructFieldHeadFloat64 return opStructFieldHeadFloat64
case opFloat64Ptr:
return opStructFieldHeadFloat64Ptr
case opString: case opString:
return opStructFieldHeadString return opStructFieldHeadString
case opStringPtr:
return opStructFieldHeadStringPtr
case opNumber:
return opStructFieldHeadNumber
case opNumberPtr:
return opStructFieldHeadNumberPtr
case opBool: case opBool:
return opStructFieldHeadBool return opStructFieldHeadBool
case opMapHead: case opBoolPtr:
return opStructFieldHeadBoolPtr
case opMap:
return opStructFieldHeadMap return opStructFieldHeadMap
case opMapHeadLoad: case opMapPtr:
return opStructFieldHeadMapLoad code.op = opMap
case opArrayHead: return opStructFieldHeadMapPtr
case opArray:
return opStructFieldHeadArray return opStructFieldHeadArray
case opSliceHead: case opArrayPtr:
code.op = opArray
return opStructFieldHeadArrayPtr
case opSlice:
return opStructFieldHeadSlice return opStructFieldHeadSlice
case opStructFieldHead: case opSlicePtr:
return opStructFieldHeadStruct code.op = opSlice
return opStructFieldHeadSlicePtr
case opMarshalJSON: case opMarshalJSON:
return opStructFieldHeadMarshalJSON return opStructFieldHeadMarshalJSON
case opMarshalJSONPtr:
return opStructFieldHeadMarshalJSONPtr
case opMarshalText: case opMarshalText:
return opStructFieldHeadMarshalText return opStructFieldHeadMarshalText
case opMarshalTextPtr:
return opStructFieldHeadMarshalTextPtr
} }
return opStructFieldHead return opStructFieldHead
} }
func encodeTypeToFieldType(ctx *encodeCompileContext, code *opcode) opType { func encodeTypeToFieldType(code *opcode) opType {
switch code.op { switch code.op {
case opPtr:
ptrNum := 1
ctx.decIndex()
c := code
for {
if code.next.op == opPtr {
ptrNum++
code = code.next
ctx.decIndex()
continue
}
break
}
c.ptrNum = ptrNum
if ptrNum > 1 {
switch code.next.op {
case opInt:
c.mask = code.next.mask
c.rshiftNum = code.next.rshiftNum
return opStructFieldIntNPtr
case opUint:
c.mask = code.next.mask
return opStructFieldUintNPtr
case opFloat32:
return opStructFieldFloat32NPtr
case opFloat64:
return opStructFieldFloat64NPtr
case opString:
return opStructFieldStringNPtr
case opBool:
return opStructFieldBoolNPtr
}
} else {
switch code.next.op {
case opInt:
c.mask = code.next.mask
c.rshiftNum = code.next.rshiftNum
return opStructFieldIntPtr
case opUint:
c.mask = code.next.mask
return opStructFieldUintPtr
case opFloat32:
return opStructFieldFloat32Ptr
case opFloat64:
return opStructFieldFloat64Ptr
case opString:
return opStructFieldStringPtr
case opBool:
return opStructFieldBoolPtr
}
}
case opInt: case opInt:
return opStructFieldInt return opStructFieldInt
case opIntPtr:
return opStructFieldIntPtr
case opUint: case opUint:
return opStructFieldUint return opStructFieldUint
case opUintPtr:
return opStructFieldUintPtr
case opFloat32: case opFloat32:
return opStructFieldFloat32 return opStructFieldFloat32
case opFloat32Ptr:
return opStructFieldFloat32Ptr
case opFloat64: case opFloat64:
return opStructFieldFloat64 return opStructFieldFloat64
case opFloat64Ptr:
return opStructFieldFloat64Ptr
case opString: case opString:
return opStructFieldString return opStructFieldString
case opStringPtr:
return opStructFieldStringPtr
case opNumber:
return opStructFieldNumber
case opNumberPtr:
return opStructFieldNumberPtr
case opBool: case opBool:
return opStructFieldBool return opStructFieldBool
case opMapHead: case opBoolPtr:
return opStructFieldBoolPtr
case opMap:
return opStructFieldMap return opStructFieldMap
case opMapHeadLoad: case opMapPtr:
return opStructFieldMapLoad code.op = opMap
case opArrayHead: return opStructFieldMapPtr
case opArray:
return opStructFieldArray return opStructFieldArray
case opSliceHead: case opArrayPtr:
code.op = opArray
return opStructFieldArrayPtr
case opSlice:
return opStructFieldSlice return opStructFieldSlice
case opStructFieldHead: case opSlicePtr:
return opStructFieldStruct code.op = opSlice
return opStructFieldSlicePtr
case opMarshalJSON: case opMarshalJSON:
return opStructFieldMarshalJSON return opStructFieldMarshalJSON
case opMarshalJSONPtr:
return opStructFieldMarshalJSONPtr
case opMarshalText: case opMarshalText:
return opStructFieldMarshalText return opStructFieldMarshalText
case opMarshalTextPtr:
return opStructFieldMarshalTextPtr
} }
return opStructField return opStructField
} }
func encodeOptimizeStructHeader(ctx *encodeCompileContext, code *opcode, tag *structTag) opType { func encodeOptimizeStructHeader(code *opcode, tag *structTag) opType {
headType := encodeTypeToHeaderType(ctx, code) headType := encodeTypeToHeaderType(code)
switch { switch {
case tag.isOmitEmpty: case tag.isOmitEmpty:
headType = headType.headToOmitEmptyHead() headType = headType.headToOmitEmptyHead()
@ -922,8 +881,8 @@ func encodeOptimizeStructHeader(ctx *encodeCompileContext, code *opcode, tag *st
return headType return headType
} }
func encodeOptimizeStructField(ctx *encodeCompileContext, code *opcode, tag *structTag) opType { func encodeOptimizeStructField(code *opcode, tag *structTag) opType {
fieldType := encodeTypeToFieldType(ctx, code) fieldType := encodeTypeToFieldType(code)
switch { switch {
case tag.isOmitEmpty: case tag.isOmitEmpty:
fieldType = fieldType.fieldToOmitEmptyField() fieldType = fieldType.fieldToOmitEmptyField()
@ -950,7 +909,7 @@ func encodeCompiledCode(ctx *encodeCompileContext) *opcode {
func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode { func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode {
fieldCode.indent-- fieldCode.indent--
op := encodeOptimizeStructHeader(ctx, valueCode, tag) op := encodeOptimizeStructHeader(valueCode, tag)
fieldCode.op = op fieldCode.op = op
fieldCode.mask = valueCode.mask fieldCode.mask = valueCode.mask
fieldCode.rshiftNum = valueCode.rshiftNum fieldCode.rshiftNum = valueCode.rshiftNum
@ -960,16 +919,35 @@ func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode
opStructFieldHeadSlice, opStructFieldHeadSlice,
opStructFieldHeadArray, opStructFieldHeadArray,
opStructFieldHeadMap, opStructFieldHeadMap,
opStructFieldHeadMapLoad,
opStructFieldHeadStruct, opStructFieldHeadStruct,
opStructFieldHeadOmitEmpty, opStructFieldHeadOmitEmpty,
opStructFieldHeadOmitEmptySlice, opStructFieldHeadOmitEmptySlice,
opStructFieldHeadStringTagSlice,
opStructFieldHeadOmitEmptyArray, opStructFieldHeadOmitEmptyArray,
opStructFieldHeadStringTagArray,
opStructFieldHeadOmitEmptyMap, opStructFieldHeadOmitEmptyMap,
opStructFieldHeadOmitEmptyMapLoad, opStructFieldHeadStringTagMap,
opStructFieldHeadOmitEmptyStruct, opStructFieldHeadOmitEmptyStruct,
opStructFieldHeadStringTag: opStructFieldHeadStringTag:
return valueCode.beforeLastCode() return valueCode.beforeLastCode()
case opStructFieldHeadSlicePtr,
opStructFieldHeadOmitEmptySlicePtr,
opStructFieldHeadStringTagSlicePtr,
opStructFieldHeadArrayPtr,
opStructFieldHeadOmitEmptyArrayPtr,
opStructFieldHeadStringTagArrayPtr,
opStructFieldHeadMapPtr,
opStructFieldHeadOmitEmptyMapPtr,
opStructFieldHeadStringTagMapPtr:
return valueCode.beforeLastCode()
case opStructFieldHeadMarshalJSONPtr,
opStructFieldHeadOmitEmptyMarshalJSONPtr,
opStructFieldHeadStringTagMarshalJSONPtr,
opStructFieldHeadMarshalTextPtr,
opStructFieldHeadOmitEmptyMarshalTextPtr,
opStructFieldHeadStringTagMarshalTextPtr:
ctx.decOpcodeIndex()
return (*opcode)(unsafe.Pointer(fieldCode))
} }
ctx.decOpcodeIndex() ctx.decOpcodeIndex()
return (*opcode)(unsafe.Pointer(fieldCode)) return (*opcode)(unsafe.Pointer(fieldCode))
@ -977,26 +955,38 @@ func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode
func encodeStructField(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode { func encodeStructField(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode {
code := (*opcode)(unsafe.Pointer(fieldCode)) code := (*opcode)(unsafe.Pointer(fieldCode))
op := encodeOptimizeStructField(ctx, valueCode, tag) op := encodeOptimizeStructField(valueCode, tag)
fieldCode.op = op fieldCode.op = op
fieldCode.ptrNum = valueCode.ptrNum fieldCode.ptrNum = valueCode.ptrNum
fieldCode.mask = valueCode.mask fieldCode.mask = valueCode.mask
fieldCode.rshiftNum = valueCode.rshiftNum fieldCode.rshiftNum = valueCode.rshiftNum
fieldCode.jmp = valueCode.jmp
switch op { switch op {
case opStructField, case opStructField,
opStructFieldSlice, opStructFieldSlice,
opStructFieldArray, opStructFieldArray,
opStructFieldMap, opStructFieldMap,
opStructFieldMapLoad,
opStructFieldStruct, opStructFieldStruct,
opStructFieldOmitEmpty, opStructFieldOmitEmpty,
opStructFieldOmitEmptySlice, opStructFieldOmitEmptySlice,
opStructFieldStringTagSlice,
opStructFieldOmitEmptyArray, opStructFieldOmitEmptyArray,
opStructFieldStringTagArray,
opStructFieldOmitEmptyMap, opStructFieldOmitEmptyMap,
opStructFieldOmitEmptyMapLoad, opStructFieldStringTagMap,
opStructFieldOmitEmptyStruct, opStructFieldOmitEmptyStruct,
opStructFieldStringTag: opStructFieldStringTag:
return valueCode.beforeLastCode() return valueCode.beforeLastCode()
case opStructFieldSlicePtr,
opStructFieldOmitEmptySlicePtr,
opStructFieldStringTagSlicePtr,
opStructFieldArrayPtr,
opStructFieldOmitEmptyArrayPtr,
opStructFieldStringTagArrayPtr,
opStructFieldMapPtr,
opStructFieldOmitEmptyMapPtr,
opStructFieldStringTagMapPtr:
return valueCode.beforeLastCode()
} }
ctx.decIndex() ctx.decIndex()
return code return code
@ -1006,7 +996,10 @@ func encodeIsNotExistsField(head *opcode) bool {
if head == nil { if head == nil {
return false return false
} }
if head.op != opStructFieldAnonymousHead { if head.op != opStructFieldHead {
return false
}
if !head.anonymousHead {
return false return false
} }
if head.next == nil { if head.next == nil {
@ -1068,14 +1061,17 @@ func encodeAnonymousStructFieldPairMap(tags structTags, named string, valueCode
removedFields := map[*opcode]struct{}{} removedFields := map[*opcode]struct{}{}
for { for {
existsKey := tags.existsKey(f.displayKey) existsKey := tags.existsKey(f.displayKey)
op := f.op.headToAnonymousHead() isHeadOp := strings.Contains(f.op.String(), "Head")
if existsKey && (f.next.op == opStructFieldPtrAnonymousHeadRecursive || f.next.op == opStructFieldAnonymousHeadRecursive) { if existsKey && strings.Contains(f.op.String(), "Recursive") {
// through // through
} else if op != f.op { } else if isHeadOp && !f.anonymousHead {
if existsKey { if existsKey {
f.op = opStructFieldAnonymousHead // TODO: need to remove this head
f.op = opStructFieldHead
f.anonymousKey = true
f.anonymousHead = true
} else if named == "" { } else if named == "" {
f.op = op f.anonymousHead = true
} }
} else if named == "" && f.op == opStructEnd { } else if named == "" && f.op == opStructEnd {
f.op = opStructAnonymousEnd f.op = opStructAnonymousEnd
@ -1121,7 +1117,7 @@ func encodeAnonymousFieldPairRecursively(named string, valueCode *opcode) map[st
f := valueCode f := valueCode
var prevAnonymousField *opcode var prevAnonymousField *opcode
for { for {
if f.displayKey != "" && strings.Contains(f.op.String(), "Anonymous") { if f.displayKey != "" && f.anonymousHead {
key := fmt.Sprintf("%s.%s", named, f.displayKey) key := fmt.Sprintf("%s.%s", named, f.displayKey)
anonymousFields[key] = append(anonymousFields[key], structFieldPair{ anonymousFields[key] = append(anonymousFields[key], structFieldPair{
prevField: prevAnonymousField, prevField: prevAnonymousField,
@ -1158,7 +1154,9 @@ func encodeOptimizeConflictAnonymousFields(anonymousFields map[string][]structFi
if !fieldPair.linked { if !fieldPair.linked {
if fieldPair.prevField == nil { if fieldPair.prevField == nil {
// head operation // head operation
fieldPair.curField.op = opStructFieldAnonymousHead fieldPair.curField.op = opStructFieldHead
fieldPair.curField.anonymousHead = true
fieldPair.curField.anonymousKey = true
} else { } else {
diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx
for i := 0; i < diff; i++ { for i := 0; i < diff; i++ {
@ -1176,7 +1174,9 @@ func encodeOptimizeConflictAnonymousFields(anonymousFields map[string][]structFi
if !fieldPair.linked { if !fieldPair.linked {
if fieldPair.prevField == nil { if fieldPair.prevField == nil {
// head operation // head operation
fieldPair.curField.op = opStructFieldAnonymousHead fieldPair.curField.op = opStructFieldHead
fieldPair.curField.anonymousHead = true
fieldPair.curField.anonymousKey = true
} else { } else {
diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx
removedFields[fieldPair.curField] = struct{}{} removedFields[fieldPair.curField] = struct{}{}
@ -1196,8 +1196,22 @@ func encodeOptimizeConflictAnonymousFields(anonymousFields map[string][]structFi
} }
} }
func encodeIsNilableType(typ *rtype) bool {
switch typ.Kind() {
case reflect.Ptr:
return true
case reflect.Interface:
return true
case reflect.Slice:
return true
case reflect.Map:
return true
default:
return false
}
}
func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) { func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) {
ctx.root = false
if code := encodeCompiledCode(ctx); code != nil { if code := encodeCompiledCode(ctx); code != nil {
return code, nil return code, nil
} }
@ -1209,7 +1223,9 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
// ^ | // ^ |
// |__________| // |__________|
fieldNum := typ.NumField() fieldNum := typ.NumField()
indirect := ifaceIndir(typ)
fieldIdx := 0 fieldIdx := 0
disableIndirectConversion := false
var ( var (
head *opcode head *opcode
code *opcode code *opcode
@ -1228,30 +1244,70 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
for i, tag := range tags { for i, tag := range tags {
field := tag.field field := tag.field
fieldType := type2rtype(field.Type) fieldType := type2rtype(field.Type)
if isPtr && i == 0 {
// head field of pointer structure at top level
// if field type is pointer and implements MarshalJSON or MarshalText,
// it need to operation of dereference of pointer.
if field.Type.Kind() == reflect.Ptr &&
(field.Type.Implements(marshalJSONType) || field.Type.Implements(marshalTextType)) {
fieldType = rtype_ptrTo(fieldType)
}
}
fieldOpcodeIndex := ctx.opcodeIndex fieldOpcodeIndex := ctx.opcodeIndex
fieldPtrIndex := ctx.ptrIndex fieldPtrIndex := ctx.ptrIndex
ctx.incIndex() ctx.incIndex()
valueCode, err := encodeCompile(ctx.withType(fieldType))
nilcheck := true
addrForMarshaler := false
isIndirectSpecialCase := isPtr && i == 0 && fieldNum == 1
isNilableType := encodeIsNilableType(fieldType)
var valueCode *opcode
switch {
case isIndirectSpecialCase && !isNilableType && encodeIsPtrMarshalJSONType(fieldType):
// *struct{ field T } => struct { field *T }
// func (*T) MarshalJSON() ([]byte, error)
// move pointer position from head to first field
code, err := encodeCompileMarshalJSON(ctx.withType(rtype_ptrTo(fieldType)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
valueCode = code
nilcheck = false
indirect = false
disableIndirectConversion = true
case isIndirectSpecialCase && !isNilableType && encodeIsPtrMarshalTextType(fieldType):
// *struct{ field T } => struct { field *T }
// func (*T) MarshalText() ([]byte, error)
// move pointer position from head to first field
code, err := encodeCompileMarshalText(ctx.withType(rtype_ptrTo(fieldType)))
if err != nil {
return nil, err
}
valueCode = code
nilcheck = false
indirect = false
disableIndirectConversion = true
case isPtr && encodeIsPtrMarshalJSONType(fieldType):
// *struct{ field T }
// func (*T) MarshalJSON() ([]byte, error)
code, err := encodeCompileMarshalJSON(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
nilcheck = false
valueCode = code
case isPtr && encodeIsPtrMarshalTextType(fieldType):
// *struct{ field T }
// func (*T) MarshalText() ([]byte, error)
code, err := encodeCompileMarshalText(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
nilcheck = false
valueCode = code
default:
code, err := encodeCompile(ctx.withType(fieldType), isPtr)
if err != nil {
return nil, err
}
valueCode = code
}
if field.Anonymous { if field.Anonymous {
if valueCode.op == opPtr && valueCode.next.op == opStructFieldRecursive {
valueCode = valueCode.next
valueCode.decOpcodeIndex()
ctx.decIndex()
valueCode.op = opStructFieldPtrHeadRecursive
}
tagKey := "" tagKey := ""
if tag.isTaggedKey { if tag.isTaggedKey {
tagKey = tag.key tagKey = tag.key
@ -1259,6 +1315,14 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
for k, v := range encodeAnonymousStructFieldPairMap(tags, tagKey, valueCode) { for k, v := range encodeAnonymousStructFieldPairMap(tags, tagKey, valueCode) {
anonymousFields[k] = append(anonymousFields[k], v...) anonymousFields[k] = append(anonymousFields[k], v...)
} }
valueCode.decIndent()
// fix issue144
if !(isPtr && strings.Contains(valueCode.op.String(), "Marshal")) {
valueCode.indirect = indirect
}
} else {
valueCode.indirect = indirect
} }
key := fmt.Sprintf(`"%s":`, tag.key) key := fmt.Sprintf(`"%s":`, tag.key)
escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key))) escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key)))
@ -1274,6 +1338,9 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
isTaggedKey: tag.isTaggedKey, isTaggedKey: tag.isTaggedKey,
displayKey: tag.key, displayKey: tag.key,
offset: field.Offset, offset: field.Offset,
indirect: indirect,
nilcheck: nilcheck,
addrForMarshaler: addrForMarshaler,
} }
if fieldIdx == 0 { if fieldIdx == 0 {
fieldCode.headIdx = fieldCode.idx fieldCode.headIdx = fieldCode.idx
@ -1333,5 +1400,17 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
delete(ctx.structTypeToCompiledCode, typeptr) delete(ctx.structTypeToCompiledCode, typeptr)
if !disableIndirectConversion && !head.indirect && isPtr {
head.indirect = true
}
return ret, nil return ret, nil
} }
func encodeIsPtrMarshalJSONType(typ *rtype) bool {
return !typ.Implements(marshalJSONType) && rtype_ptrTo(typ).Implements(marshalJSONType)
}
func encodeIsPtrMarshalTextType(typ *rtype) bool {
return !typ.Implements(marshalTextType) && rtype_ptrTo(typ).Implements(marshalTextType)
}

View File

@ -18,7 +18,6 @@ func encodeCompileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
code, err := encodeCompileHead(&encodeCompileContext{ code, err := encodeCompileHead(&encodeCompileContext{
typ: copiedType, typ: copiedType,
root: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{}, structTypeToCompiledCode: map[uintptr]*compiledCode{},
}) })
if err != nil { if err != nil {

View File

@ -26,7 +26,6 @@ func encodeCompileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
code, err := encodeCompileHead(&encodeCompileContext{ code, err := encodeCompileHead(&encodeCompileContext{
typ: copiedType, typ: copiedType,
root: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{}, structTypeToCompiledCode: map[uintptr]*compiledCode{},
}) })
if err != nil { if err != nil {

View File

@ -63,7 +63,6 @@ func releaseMapContext(c *encodeMapContext) {
type encodeCompileContext struct { type encodeCompileContext struct {
typ *rtype typ *rtype
root bool
opcodeIndex int opcodeIndex int
ptrIndex int ptrIndex int
indent int indent int
@ -75,7 +74,6 @@ type encodeCompileContext struct {
func (c *encodeCompileContext) context() *encodeCompileContext { func (c *encodeCompileContext) context() *encodeCompileContext {
return &encodeCompileContext{ return &encodeCompileContext{
typ: c.typ, typ: c.typ,
root: c.root,
opcodeIndex: c.opcodeIndex, opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex, ptrIndex: c.ptrIndex,
indent: c.indent, indent: c.indent,

View File

@ -18,7 +18,10 @@ type opcode struct {
displayKey string // key text to display displayKey string // key text to display
isTaggedKey bool // whether tagged key isTaggedKey bool // whether tagged key
anonymousKey bool // whether anonymous key anonymousKey bool // whether anonymous key
root bool // whether root anonymousHead bool // whether anonymous head or not
indirect bool // whether indirect or not
nilcheck bool // whether needs to nilcheck or not
addrForMarshaler bool // whether needs to addr for marshaler or not
rshiftNum uint8 // use to take bit for judging whether negative integer or not rshiftNum uint8 // use to take bit for judging whether negative integer or not
mask uint64 // mask for number mask uint64 // mask for number
indent int // indent number indent int // indent number
@ -90,7 +93,10 @@ func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode {
rshiftNum: c.rshiftNum, rshiftNum: c.rshiftNum,
isTaggedKey: c.isTaggedKey, isTaggedKey: c.isTaggedKey,
anonymousKey: c.anonymousKey, anonymousKey: c.anonymousKey,
root: c.root, anonymousHead: c.anonymousHead,
indirect: c.indirect,
nilcheck: c.nilcheck,
addrForMarshaler: c.addrForMarshaler,
indent: c.indent, indent: c.indent,
idx: c.idx, idx: c.idx,
headIdx: c.headIdx, headIdx: c.headIdx,
@ -172,6 +178,18 @@ func (c *opcode) decOpcodeIndex() {
} }
} }
func (c *opcode) decIndent() {
for code := c; code.op != opEnd; {
code.indent--
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
default:
code = code.next
}
}
}
func (c *opcode) dumpHead(code *opcode) string { func (c *opcode) dumpHead(code *opcode) string {
var length uintptr var length uintptr
if code.op.codeType() == codeArrayHead { if code.op.codeType() == codeArrayHead {
@ -360,7 +378,7 @@ func newSliceHeaderCode(ctx *encodeCompileContext) *opcode {
ctx.incPtrIndex() ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex) length := opcodeOffset(ctx.ptrIndex)
return &opcode{ return &opcode{
op: opSliceHead, op: opSlice,
displayIdx: ctx.opcodeIndex, displayIdx: ctx.opcodeIndex,
idx: idx, idx: idx,
headIdx: idx, headIdx: idx,
@ -388,7 +406,7 @@ func newArrayHeaderCode(ctx *encodeCompileContext, alen int) *opcode {
ctx.incPtrIndex() ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex) elemIdx := opcodeOffset(ctx.ptrIndex)
return &opcode{ return &opcode{
op: opArrayHead, op: opArray,
displayIdx: ctx.opcodeIndex, displayIdx: ctx.opcodeIndex,
idx: idx, idx: idx,
headIdx: idx, headIdx: idx,
@ -406,17 +424,12 @@ func newArrayElemCode(ctx *encodeCompileContext, head *opcode, length int, size
elemIdx: head.elemIdx, elemIdx: head.elemIdx,
headIdx: head.headIdx, headIdx: head.headIdx,
length: uintptr(length), length: uintptr(length),
indent: ctx.indent,
size: size, size: size,
} }
} }
func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode { func newMapHeaderCode(ctx *encodeCompileContext) *opcode {
var op opType
if withLoad {
op = opMapHeadLoad
} else {
op = opMapHead
}
idx := opcodeOffset(ctx.ptrIndex) idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex() ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex) elemIdx := opcodeOffset(ctx.ptrIndex)
@ -425,7 +438,7 @@ func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode {
ctx.incPtrIndex() ctx.incPtrIndex()
mapIter := opcodeOffset(ctx.ptrIndex) mapIter := opcodeOffset(ctx.ptrIndex)
return &opcode{ return &opcode{
op: op, op: opMap,
typ: ctx.typ, typ: ctx.typ,
displayIdx: ctx.opcodeIndex, displayIdx: ctx.opcodeIndex,
idx: idx, idx: idx,
@ -482,7 +495,6 @@ func newInterfaceCode(ctx *encodeCompileContext) *opcode {
displayIdx: ctx.opcodeIndex, displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex), idx: opcodeOffset(ctx.ptrIndex),
indent: ctx.indent, indent: ctx.indent,
root: ctx.root,
next: newEndOp(ctx), next: newEndOp(ctx),
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package json_test
import ( import (
"bytes" "bytes"
"encoding" "encoding"
stdjson "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@ -1674,3 +1675,117 @@ func TestOmitEmpty(t *testing.T) {
t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected)
} }
} }
type testNullStr string
func (v *testNullStr) MarshalJSON() ([]byte, error) {
if *v == "" {
return []byte("null"), nil
}
return []byte(*v), nil
}
func TestIssue147(t *testing.T) {
type T struct {
Field1 string `json:"field1"`
Field2 testNullStr `json:"field2,omitempty"`
}
got, err := json.Marshal(T{
Field1: "a",
Field2: "b",
})
if err != nil {
t.Fatal(err)
}
expect, _ := stdjson.Marshal(T{
Field1: "a",
Field2: "b",
})
if !bytes.Equal(expect, got) {
t.Fatalf("expect %q but got %q", string(expect), string(got))
}
}
type testIssue144 struct {
name string
number int64
}
func (v *testIssue144) MarshalJSON() ([]byte, error) {
if v.name != "" {
return json.Marshal(v.name)
}
return json.Marshal(v.number)
}
func TestIssue144(t *testing.T) {
type Embeded struct {
Field *testIssue144 `json:"field,omitempty"`
}
type T struct {
Embeded
}
{
v := T{
Embeded: Embeded{Field: &testIssue144{name: "hoge"}},
}
got, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
expect, _ := stdjson.Marshal(v)
if !bytes.Equal(expect, got) {
t.Fatalf("expect %q but got %q", string(expect), string(got))
}
}
{
v := &T{
Embeded: Embeded{Field: &testIssue144{name: "hoge"}},
}
got, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
expect, _ := stdjson.Marshal(v)
if !bytes.Equal(expect, got) {
t.Fatalf("expect %q but got %q", string(expect), string(got))
}
}
}
func TestIssue118(t *testing.T) {
type data struct {
Columns []string `json:"columns"`
Rows1 [][]string `json:"rows1"`
Rows2 [][]string `json:"rows2"`
}
v := data{Columns: []string{"1", "2", "3"}}
got, err := json.MarshalIndent(v, "", " ")
if err != nil {
t.Fatal(err)
}
expect, _ := stdjson.MarshalIndent(v, "", " ")
if !bytes.Equal(expect, got) {
t.Fatalf("expect %q but got %q", string(expect), string(got))
}
}
func TestIssue104(t *testing.T) {
type A struct {
Field1 string
Field2 int
Field3 float64
}
type T struct {
Field A
}
got, err := json.Marshal(T{})
if err != nil {
t.Fatal(err)
}
expect, _ := stdjson.Marshal(T{})
if !bytes.Equal(expect, got) {
t.Fatalf("expect %q but got %q", string(expect), string(got))
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

36
json.go
View File

@ -2,8 +2,8 @@ package json
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"strconv"
) )
// Marshaler is the interface implemented by types that // Marshaler is the interface implemented by types that
@ -281,39 +281,7 @@ func UnmarshalNoEscape(data []byte, v interface{}) error {
type Token interface{} type Token interface{}
// A Number represents a JSON number literal. // A Number represents a JSON number literal.
type Number string type Number = json.Number
// String returns the literal text of the number.
func (n Number) String() string { return string(n) }
// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
}
// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
func (n Number) MarshalJSON() ([]byte, error) {
if n == "" {
return []byte("0"), nil
}
if _, err := n.Float64(); err != nil {
return nil, err
}
return []byte(n), nil
}
func (n *Number) UnmarshalJSON(b []byte) error {
s := string(b)
if _, err := strconv.ParseFloat(s, 64); err != nil {
return &SyntaxError{msg: err.Error()}
}
*n = Number(s)
return nil
}
// RawMessage is a raw encoded JSON value. // RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can // It implements Marshaler and Unmarshaler and can

View File

@ -244,6 +244,10 @@ func (t *rtype) Out(i int) reflect.Type {
return rtype_Out(t, i) return rtype_Out(t, i)
} }
//go:linkname ifaceIndir reflect.ifaceIndir
//go:noescape
func ifaceIndir(*rtype) bool
//go:linkname rtype2type reflect.toType //go:linkname rtype2type reflect.toType
//go:noescape //go:noescape
func rtype2type(t *rtype) reflect.Type func rtype2type(t *rtype) reflect.Type