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
on: [push, pull_request]
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:
name: Test
strategy:
matrix:
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 }}
steps:
- name: setup Go ${{ matrix.go-version }}
@ -30,7 +38,7 @@ jobs:
- name: setup Go
uses: actions/setup-go@v2
with:
go-version: 1.15
go-version: 1.16
- name: checkout
uses: actions/checkout@v2
- name: measure coverage

View File

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

@ -5,20 +5,23 @@ import (
stdjson "encoding/json"
)
func intptr(v int) *int { return &v }
func int8ptr(v int8) *int8 { return &v }
func int16ptr(v int16) *int16 { return &v }
func int32ptr(v int32) *int32 { return &v }
func int64ptr(v int64) *int64 { return &v }
func uptr(v uint) *uint { return &v }
func uint8ptr(v uint8) *uint8 { return &v }
func uint16ptr(v uint16) *uint16 { return &v }
func uint32ptr(v uint32) *uint32 { return &v }
func uint64ptr(v uint64) *uint64 { return &v }
func float32ptr(v float32) *float32 { return &v }
func float64ptr(v float64) *float64 { return &v }
func stringptr(v string) *string { return &v }
func boolptr(v bool) *bool { return &v }
func intptr(v int) *int { return &v }
func int8ptr(v int8) *int8 { return &v }
func int16ptr(v int16) *int16 { return &v }
func int32ptr(v int32) *int32 { return &v }
func int64ptr(v int64) *int64 { return &v }
func uptr(v uint) *uint { return &v }
func uint8ptr(v uint8) *uint8 { return &v }
func uint16ptr(v uint16) *uint16 { return &v }
func uint32ptr(v uint32) *uint32 { return &v }
func uint64ptr(v uint64) *uint64 { return &v }
func float32ptr(v float32) *float32 { return &v }
func float64ptr(v float64) *float64 { return &v }
func stringptr(v string) *string { 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 {
var buf bytes.Buffer

View File

@ -408,6 +408,29 @@ func TestCoverInt16(t *testing.T) {
}{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
{
name: "PtrHeadInt16NilMultiFields",

View File

@ -1774,11 +1774,11 @@ func TestCoverInt(t *testing.T) {
enc.SetIndent("", " ")
}
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)
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("", " ")
}
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)
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("", " ")
}
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)
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:
return decodeCompileUint64(typ, structName, fieldName)
case reflect.String:
return decodeCompileString(structName, fieldName)
return decodeCompileString(typ, structName, fieldName)
case reflect.Bool:
return decodeCompileBool(structName, fieldName)
case reflect.Float32:
@ -203,7 +203,12 @@ func decodeCompileFloat64(structName, fieldName string) (decoder, error) {
}), 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
}

View File

@ -1,42 +1,118 @@
package json
import (
"strconv"
"unsafe"
)
type numberDecoder struct {
*floatDecoder
op func(unsafe.Pointer, Number)
structName string
fieldName string
stringDecoder *stringDecoder
op func(unsafe.Pointer, Number)
structName string
fieldName string
}
func newNumberDecoder(structName, fieldName string, op func(unsafe.Pointer, Number)) *numberDecoder {
return &numberDecoder{
floatDecoder: newFloatDecoder(structName, fieldName, nil),
op: op,
structName: structName,
fieldName: fieldName,
stringDecoder: newStringDecoder(structName, fieldName),
op: op,
structName: structName,
fieldName: fieldName,
}
}
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 {
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)))
s.reset()
return nil
}
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 {
return 0, err
}
if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil {
return 0, &SyntaxError{msg: err.Error(), Offset: c}
}
cursor = c
s := *(*string)(unsafe.Pointer(&bytes))
d.op(p, Number(s))
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
ptr: new(json.Number),
err: json.NewSyntaxError(
`json: invalid character v as null`,
`json: json.Number unexpected end of JSON input`,
1,
),
},
{
in: `"invalid"`, // 144
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
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
ptr: new(struct {
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
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.

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 (
"bytes"
"encoding"
"encoding/base64"
"fmt"
"io"
"math"
"reflect"
"strconv"
"strings"
"sync"
"unsafe"
)
@ -358,7 +362,147 @@ func encodeByteSlice(b []byte, src []byte) []byte {
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 {
b = append(b, ctx.prefix...)
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
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -9,19 +9,22 @@ import (
const uintptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
type opcode struct {
op opType // operation type
typ *rtype // go type
displayIdx int // opcode index
key []byte // struct field key
escapedKey []byte // struct field key ( HTML escaped )
ptrNum int // pointer number: e.g. double pointer is 2.
displayKey string // key text to display
isTaggedKey bool // whether tagged key
anonymousKey bool // whether anonymous key
root bool // whether root
rshiftNum uint8 // use to take bit for judging whether negative integer or not
mask uint64 // mask for number
indent int // indent number
op opType // operation type
typ *rtype // go type
displayIdx int // opcode index
key []byte // struct field key
escapedKey []byte // struct field key ( HTML escaped )
ptrNum int // pointer number: e.g. double pointer is 2.
displayKey string // key text to display
isTaggedKey bool // whether tagged key
anonymousKey bool // whether anonymous key
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
mask uint64 // mask for number
indent int // indent number
idx uintptr // offset to access ptr
headIdx uintptr // offset to access slice/struct head
@ -79,27 +82,30 @@ func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode {
return code
}
copied := &opcode{
op: c.op,
typ: c.typ,
displayIdx: c.displayIdx,
key: c.key,
escapedKey: c.escapedKey,
displayKey: c.displayKey,
ptrNum: c.ptrNum,
mask: c.mask,
rshiftNum: c.rshiftNum,
isTaggedKey: c.isTaggedKey,
anonymousKey: c.anonymousKey,
root: c.root,
indent: c.indent,
idx: c.idx,
headIdx: c.headIdx,
elemIdx: c.elemIdx,
length: c.length,
mapIter: c.mapIter,
mapPos: c.mapPos,
offset: c.offset,
size: c.size,
op: c.op,
typ: c.typ,
displayIdx: c.displayIdx,
key: c.key,
escapedKey: c.escapedKey,
displayKey: c.displayKey,
ptrNum: c.ptrNum,
mask: c.mask,
rshiftNum: c.rshiftNum,
isTaggedKey: c.isTaggedKey,
anonymousKey: c.anonymousKey,
anonymousHead: c.anonymousHead,
indirect: c.indirect,
nilcheck: c.nilcheck,
addrForMarshaler: c.addrForMarshaler,
indent: c.indent,
idx: c.idx,
headIdx: c.headIdx,
elemIdx: c.elemIdx,
length: c.length,
mapIter: c.mapIter,
mapPos: c.mapPos,
offset: c.offset,
size: c.size,
}
codeMap[addr] = copied
copied.mapKey = c.mapKey.copy(codeMap)
@ -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 {
var length uintptr
if code.op.codeType() == codeArrayHead {
@ -360,7 +378,7 @@ func newSliceHeaderCode(ctx *encodeCompileContext) *opcode {
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opSliceHead,
op: opSlice,
displayIdx: ctx.opcodeIndex,
idx: idx,
headIdx: idx,
@ -388,7 +406,7 @@ func newArrayHeaderCode(ctx *encodeCompileContext, alen int) *opcode {
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opArrayHead,
op: opArray,
displayIdx: ctx.opcodeIndex,
idx: idx,
headIdx: idx,
@ -406,17 +424,12 @@ func newArrayElemCode(ctx *encodeCompileContext, head *opcode, length int, size
elemIdx: head.elemIdx,
headIdx: head.headIdx,
length: uintptr(length),
indent: ctx.indent,
size: size,
}
}
func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode {
var op opType
if withLoad {
op = opMapHeadLoad
} else {
op = opMapHead
}
func newMapHeaderCode(ctx *encodeCompileContext) *opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
@ -425,7 +438,7 @@ func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode {
ctx.incPtrIndex()
mapIter := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: op,
op: opMap,
typ: ctx.typ,
displayIdx: ctx.opcodeIndex,
idx: idx,
@ -482,7 +495,6 @@ func newInterfaceCode(ctx *encodeCompileContext) *opcode {
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
indent: ctx.indent,
root: ctx.root,
next: newEndOp(ctx),
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package json_test
import (
"bytes"
"encoding"
stdjson "encoding/json"
"errors"
"fmt"
"log"
@ -1674,3 +1675,117 @@ func TestOmitEmpty(t *testing.T) {
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 (
"bytes"
"encoding/json"
"errors"
"strconv"
)
// Marshaler is the interface implemented by types that
@ -281,39 +281,7 @@ func UnmarshalNoEscape(data []byte, v interface{}) error {
type Token interface{}
// A Number represents a JSON number literal.
type Number string
// 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
}
type Number = json.Number
// RawMessage is a raw encoded JSON value.
// 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)
}
//go:linkname ifaceIndir reflect.ifaceIndir
//go:noescape
func ifaceIndir(*rtype) bool
//go:linkname rtype2type reflect.toType
//go:noescape
func rtype2type(t *rtype) reflect.Type