Merge pull request #32 from goccy/feature/support-string-tag

Support string tag
This commit is contained in:
Masaaki Goshima 2020-08-20 12:59:52 +09:00 committed by GitHub
commit f30c85fcfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 4255 additions and 573 deletions

View File

@ -25,11 +25,16 @@ type headType struct {
OmitEmptyPtrHead string OmitEmptyPtrHead string
AnonymousOmitEmptyHead string AnonymousOmitEmptyHead string
AnonymousOmitEmptyPtrHead string AnonymousOmitEmptyPtrHead string
StringTagHead string
StringTagPtrHead string
AnonymousStringTagHead string
AnonymousStringTagPtrHead string
} }
type fieldType struct { type fieldType struct {
Field string Field string
OmitEmptyField string OmitEmptyField string
StringTagField string
} }
func _main() error { func _main() error {
@ -93,6 +98,10 @@ func (t opType) headToPtrHead() opType {
return op{{ $type.OmitEmptyPtrHead }} return op{{ $type.OmitEmptyPtrHead }}
case op{{ $type.AnonymousOmitEmptyHead }}: case op{{ $type.AnonymousOmitEmptyHead }}:
return op{{ $type.AnonymousOmitEmptyPtrHead }} return op{{ $type.AnonymousOmitEmptyPtrHead }}
case op{{ $type.StringTagHead }}:
return op{{ $type.StringTagPtrHead }}
case op{{ $type.AnonymousStringTagHead }}:
return op{{ $type.AnonymousStringTagPtrHead }}
{{- end }} {{- end }}
} }
return t return t
@ -109,6 +118,10 @@ func (t opType) headToAnonymousHead() opType {
return op{{ $type.AnonymousOmitEmptyHead }} return op{{ $type.AnonymousOmitEmptyHead }}
case op{{ $type.OmitEmptyPtrHead }}: case op{{ $type.OmitEmptyPtrHead }}:
return op{{ $type.AnonymousOmitEmptyPtrHead }} return op{{ $type.AnonymousOmitEmptyPtrHead }}
case op{{ $type.StringTagHead }}:
return op{{ $type.AnonymousStringTagHead }}
case op{{ $type.StringTagPtrHead }}:
return op{{ $type.AnonymousStringTagPtrHead }}
{{- end }} {{- end }}
} }
return t return t
@ -126,6 +139,18 @@ func (t opType) headToOmitEmptyHead() opType {
return t return t
} }
func (t opType) headToStringTagHead() opType {
switch t {
{{- range $type := .HeadTypes }}
case op{{ $type.Head }}:
return op{{ $type.StringTagHead }}
case op{{ $type.PtrHead }}:
return op{{ $type.StringTagPtrHead }}
{{- end }}
}
return t
}
func (t opType) ptrHeadToHead() opType { func (t opType) ptrHeadToHead() opType {
switch t { switch t {
{{- range $type := .HeadTypes }} {{- range $type := .HeadTypes }}
@ -137,6 +162,10 @@ func (t opType) ptrHeadToHead() opType {
return op{{ $type.OmitEmptyHead }} return op{{ $type.OmitEmptyHead }}
case op{{ $type.AnonymousOmitEmptyPtrHead }}: case op{{ $type.AnonymousOmitEmptyPtrHead }}:
return op{{ $type.AnonymousOmitEmptyHead }} return op{{ $type.AnonymousOmitEmptyHead }}
case op{{ $type.StringTagPtrHead }}:
return op{{ $type.StringTagHead }}
case op{{ $type.AnonymousStringTagPtrHead }}:
return op{{ $type.AnonymousStringTagHead }}
{{- end }} {{- end }}
} }
return t return t
@ -152,6 +181,16 @@ func (t opType) fieldToOmitEmptyField() opType {
return t return t
} }
func (t opType) fieldToStringTagField() opType {
switch t {
{{- range $type := .FieldTypes }}
case op{{ $type.Field }}:
return op{{ $type.StringTagField }}
{{- end }}
}
return t
}
`) `)
if err != nil { if err != nil {
return err return err
@ -199,14 +238,19 @@ func (t opType) fieldToOmitEmptyField() opType {
{"MapEnd", "MapEndIndent", "Op"}, {"MapEnd", "MapEndIndent", "Op"},
{"StructFieldHead", "StructFieldHeadIndent", "StructField"}, {"StructFieldHead", "StructFieldHeadIndent", "StructField"},
{"StructFieldHeadOmitEmpty", "StructFieldHeadOmitEmptyIndent", "StructField"}, {"StructFieldHeadOmitEmpty", "StructFieldHeadOmitEmptyIndent", "StructField"},
{"StructFieldHeadStringTag", "StructFieldHeadStringTagIndent", "StructField"},
{"StructFieldAnonymousHead", "StructFieldAnonymousHeadIndent", "StructField"}, {"StructFieldAnonymousHead", "StructFieldAnonymousHeadIndent", "StructField"},
{"StructFieldAnonymousHeadOmitEmpty", "StructFieldAnonymousHeadOmitEmptyIndent", "StructField"}, {"StructFieldAnonymousHeadOmitEmpty", "StructFieldAnonymousHeadOmitEmptyIndent", "StructField"},
{"StructFieldPtrAnonymousHeadOmitEmpty", "StructFieldPtrAnonymousHeadOmitEmptyIndent", "StructField"}, {"StructFieldPtrAnonymousHeadOmitEmpty", "StructFieldPtrAnonymousHeadOmitEmptyIndent", "StructField"},
{"StructFieldAnonymousHeadStringTag", "StructFieldAnonymousHeadStringTagIndent", "StructField"},
{"StructFieldPtrAnonymousHeadStringTag", "StructFieldPtrAnonymousHeadStringTagIndent", "StructField"},
{"StructFieldPtrHead", "StructFieldPtrHeadIndent", "StructField"}, {"StructFieldPtrHead", "StructFieldPtrHeadIndent", "StructField"},
{"StructFieldPtrHeadOmitEmpty", "StructFieldPtrHeadOmitEmptyIndent", "StructField"}, {"StructFieldPtrHeadOmitEmpty", "StructFieldPtrHeadOmitEmptyIndent", "StructField"},
{"StructFieldPtrHeadStringTag", "StructFieldPtrHeadStringTagIndent", "StructField"},
{"StructFieldPtrAnonymousHead", "StructFieldPtrAnonymousHeadIndent", "StructField"}, {"StructFieldPtrAnonymousHead", "StructFieldPtrAnonymousHeadIndent", "StructField"},
{"StructField", "StructFieldIndent", "StructField"}, {"StructField", "StructFieldIndent", "StructField"},
{"StructFieldOmitEmpty", "StructFieldOmitEmptyIndent", "StructField"}, {"StructFieldOmitEmpty", "StructFieldOmitEmptyIndent", "StructField"},
{"StructFieldStringTag", "StructFieldStringTagIndent", "StructField"},
{"StructFieldRecursive", "StructFieldRecursiveIndent", "StructFieldRecursive"}, {"StructFieldRecursive", "StructFieldRecursiveIndent", "StructFieldRecursive"},
{"StructEnd", "StructEndIndent", "StructField"}, {"StructEnd", "StructEndIndent", "StructField"},
{"StructAnonymousEnd", "StructAnonymousEndIndent", "StructField"}, {"StructAnonymousEnd", "StructAnonymousEndIndent", "StructField"},
@ -221,14 +265,19 @@ func (t opType) fieldToOmitEmptyField() opType {
for _, prefix := range []string{ for _, prefix := range []string{
"StructFieldHead", "StructFieldHead",
"StructFieldHeadOmitEmpty", "StructFieldHeadOmitEmpty",
"StructFieldHeadStringTag",
"StructFieldAnonymousHead", "StructFieldAnonymousHead",
"StructFieldAnonymousHeadOmitEmpty", "StructFieldAnonymousHeadOmitEmpty",
"StructFieldAnonymousHeadStringTag",
"StructFieldPtrHead", "StructFieldPtrHead",
"StructFieldPtrHeadOmitEmpty", "StructFieldPtrHeadOmitEmpty",
"StructFieldPtrHeadStringTag",
"StructFieldPtrAnonymousHead", "StructFieldPtrAnonymousHead",
"StructFieldPtrAnonymousHeadOmitEmpty", "StructFieldPtrAnonymousHeadOmitEmpty",
"StructFieldPtrAnonymousHeadStringTag",
"StructField", "StructField",
"StructFieldOmitEmpty", "StructFieldOmitEmpty",
"StructFieldStringTag",
} { } {
for _, typ := range primitiveTypesUpper { for _, typ := range primitiveTypesUpper {
opTypes = append(opTypes, opType{ opTypes = append(opTypes, opType{
@ -252,8 +301,12 @@ func (t opType) fieldToOmitEmptyField() opType {
AnonymousPtrHead: "StructFieldPtrAnonymousHead", AnonymousPtrHead: "StructFieldPtrAnonymousHead",
OmitEmptyHead: "StructFieldHeadOmitEmpty", OmitEmptyHead: "StructFieldHeadOmitEmpty",
OmitEmptyPtrHead: "StructFieldPtrHeadOmitEmpty", OmitEmptyPtrHead: "StructFieldPtrHeadOmitEmpty",
StringTagHead: "StructFieldHeadStringTag",
StringTagPtrHead: "StructFieldPtrHeadStringTag",
AnonymousOmitEmptyHead: "StructFieldAnonymousHeadOmitEmpty", AnonymousOmitEmptyHead: "StructFieldAnonymousHeadOmitEmpty",
AnonymousOmitEmptyPtrHead: "StructFieldPtrAnonymousHeadOmitEmpty", AnonymousOmitEmptyPtrHead: "StructFieldPtrAnonymousHeadOmitEmpty",
AnonymousStringTagHead: "StructFieldAnonymousHeadStringTag",
AnonymousStringTagPtrHead: "StructFieldPtrAnonymousHeadStringTag",
} }
headTypes := []headType{base} headTypes := []headType{base}
for _, prim := range primitiveTypesUpper { for _, prim := range primitiveTypesUpper {
@ -266,6 +319,10 @@ func (t opType) fieldToOmitEmptyField() opType {
OmitEmptyPtrHead: fmt.Sprintf("%s%s", base.OmitEmptyPtrHead, prim), OmitEmptyPtrHead: fmt.Sprintf("%s%s", base.OmitEmptyPtrHead, prim),
AnonymousOmitEmptyHead: fmt.Sprintf("%s%s", base.AnonymousOmitEmptyHead, prim), AnonymousOmitEmptyHead: fmt.Sprintf("%s%s", base.AnonymousOmitEmptyHead, prim),
AnonymousOmitEmptyPtrHead: fmt.Sprintf("%s%s", base.AnonymousOmitEmptyPtrHead, prim), AnonymousOmitEmptyPtrHead: fmt.Sprintf("%s%s", base.AnonymousOmitEmptyPtrHead, prim),
StringTagHead: fmt.Sprintf("%s%s", base.StringTagHead, prim),
StringTagPtrHead: fmt.Sprintf("%s%s", base.StringTagPtrHead, prim),
AnonymousStringTagHead: fmt.Sprintf("%s%s", base.AnonymousStringTagHead, prim),
AnonymousStringTagPtrHead: fmt.Sprintf("%s%s", base.AnonymousStringTagPtrHead, prim),
}) })
} }
for _, typ := range headTypes { for _, typ := range headTypes {
@ -278,24 +335,31 @@ func (t opType) fieldToOmitEmptyField() opType {
OmitEmptyPtrHead: fmt.Sprintf("%sIndent", typ.OmitEmptyPtrHead), OmitEmptyPtrHead: fmt.Sprintf("%sIndent", typ.OmitEmptyPtrHead),
AnonymousOmitEmptyHead: fmt.Sprintf("%sIndent", typ.AnonymousOmitEmptyHead), AnonymousOmitEmptyHead: fmt.Sprintf("%sIndent", typ.AnonymousOmitEmptyHead),
AnonymousOmitEmptyPtrHead: fmt.Sprintf("%sIndent", typ.AnonymousOmitEmptyPtrHead), AnonymousOmitEmptyPtrHead: fmt.Sprintf("%sIndent", typ.AnonymousOmitEmptyPtrHead),
StringTagHead: fmt.Sprintf("%sIndent", typ.StringTagHead),
StringTagPtrHead: fmt.Sprintf("%sIndent", typ.StringTagPtrHead),
AnonymousStringTagHead: fmt.Sprintf("%sIndent", typ.AnonymousStringTagHead),
AnonymousStringTagPtrHead: fmt.Sprintf("%sIndent", typ.AnonymousStringTagPtrHead),
}) })
} }
baseField := fieldType{ baseField := fieldType{
Field: "StructField", Field: "StructField",
OmitEmptyField: "StructFieldOmitEmpty", OmitEmptyField: "StructFieldOmitEmpty",
StringTagField: "StructFieldStringTag",
} }
fieldTypes := []fieldType{baseField} fieldTypes := []fieldType{baseField}
for _, prim := range primitiveTypesUpper { for _, prim := range primitiveTypesUpper {
fieldTypes = append(fieldTypes, fieldType{ fieldTypes = append(fieldTypes, fieldType{
Field: fmt.Sprintf("%s%s", baseField.Field, prim), Field: fmt.Sprintf("%s%s", baseField.Field, prim),
OmitEmptyField: fmt.Sprintf("%s%s", baseField.OmitEmptyField, prim), OmitEmptyField: fmt.Sprintf("%s%s", baseField.OmitEmptyField, prim),
StringTagField: fmt.Sprintf("%s%s", baseField.StringTagField, prim),
}) })
} }
for _, typ := range fieldTypes { for _, typ := range fieldTypes {
fieldTypes = append(fieldTypes, fieldType{ fieldTypes = append(fieldTypes, fieldType{
Field: fmt.Sprintf("%sIndent", typ.Field), Field: fmt.Sprintf("%sIndent", typ.Field),
OmitEmptyField: fmt.Sprintf("%sIndent", typ.OmitEmptyField), OmitEmptyField: fmt.Sprintf("%sIndent", typ.OmitEmptyField),
StringTagField: fmt.Sprintf("%sIndent", typ.StringTagField),
}) })
} }

View File

@ -7,20 +7,31 @@ import (
) )
func (d *Decoder) compileHead(typ *rtype) (decoder, error) { func (d *Decoder) compileHead(typ *rtype) (decoder, error) {
if typ.Implements(unmarshalJSONType) { switch {
case typ.Implements(unmarshalJSONType):
return newUnmarshalJSONDecoder(typ), nil return newUnmarshalJSONDecoder(typ), nil
} else if typ.Implements(unmarshalTextType) { case rtype_ptrTo(typ).Implements(marshalJSONType):
return newUnmarshalJSONDecoder(rtype_ptrTo(typ)), nil
case typ.Implements(unmarshalTextType):
return newUnmarshalTextDecoder(typ), nil return newUnmarshalTextDecoder(typ), nil
case rtype_ptrTo(typ).Implements(unmarshalTextType):
return newUnmarshalTextDecoder(rtype_ptrTo(typ)), nil
} }
return d.compile(typ.Elem()) return d.compile(typ.Elem())
} }
func (d *Decoder) compile(typ *rtype) (decoder, error) { func (d *Decoder) compile(typ *rtype) (decoder, error) {
if typ.Implements(unmarshalJSONType) { switch {
case typ.Implements(unmarshalJSONType):
return newUnmarshalJSONDecoder(typ), nil return newUnmarshalJSONDecoder(typ), nil
} else if typ.Implements(unmarshalTextType) { case rtype_ptrTo(typ).Implements(marshalJSONType):
return newUnmarshalJSONDecoder(rtype_ptrTo(typ)), nil
case typ.Implements(unmarshalTextType):
return newUnmarshalTextDecoder(typ), nil return newUnmarshalTextDecoder(typ), nil
case rtype_ptrTo(typ).Implements(unmarshalTextType):
return newUnmarshalTextDecoder(rtype_ptrTo(typ)), nil
} }
switch typ.Kind() { switch typ.Kind() {
case reflect.Ptr: case reflect.Ptr:
return d.compilePtr(typ) return d.compilePtr(typ)
@ -34,6 +45,8 @@ func (d *Decoder) compile(typ *rtype) (decoder, error) {
return d.compileMap(typ) return d.compileMap(typ)
case reflect.Interface: case reflect.Interface:
return d.compileInterface(typ) return d.compileInterface(typ)
case reflect.Uintptr:
return d.compileUint()
case reflect.Int: case reflect.Int:
return d.compileInt() return d.compileInt()
case reflect.Int8: case reflect.Int8:
@ -188,46 +201,26 @@ func (d *Decoder) compileInterface(typ *rtype) (decoder, error) {
return newInterfaceDecoder(typ), nil return newInterfaceDecoder(typ), nil
} }
func (d *Decoder) getTag(field reflect.StructField) string {
return field.Tag.Get("json")
}
func (d *Decoder) isIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" && !field.Anonymous {
// private field
return true
}
tag := d.getTag(field)
if tag == "-" {
return true
}
return false
}
func (d *Decoder) compileStruct(typ *rtype) (decoder, error) { func (d *Decoder) compileStruct(typ *rtype) (decoder, error) {
fieldNum := typ.NumField() fieldNum := typ.NumField()
fieldMap := map[string]*structFieldSet{} fieldMap := map[string]*structFieldSet{}
for i := 0; i < fieldNum; i++ { for i := 0; i < fieldNum; i++ {
field := typ.Field(i) field := typ.Field(i)
if d.isIgnoredStructField(field) { if isIgnoredStructField(field) {
continue continue
} }
keyName := field.Name tag := structTagFromField(field)
tag := d.getTag(field)
opts := strings.Split(tag, ",")
if len(opts) > 0 {
if opts[0] != "" {
keyName = opts[0]
}
}
dec, err := d.compile(type2rtype(field.Type)) dec, err := d.compile(type2rtype(field.Type))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if tag.isString {
dec = newWrappedStringDecoder(dec)
}
fieldSet := &structFieldSet{dec: dec, offset: field.Offset} fieldSet := &structFieldSet{dec: dec, offset: field.Offset}
fieldMap[field.Name] = fieldSet fieldMap[field.Name] = fieldSet
fieldMap[keyName] = fieldSet fieldMap[tag.key] = fieldSet
fieldMap[strings.ToLower(keyName)] = fieldSet fieldMap[strings.ToLower(tag.key)] = fieldSet
} }
return newStructDecoder(fieldMap), nil return newStructDecoder(fieldMap), nil
} }

View File

@ -33,85 +33,6 @@ var (
) )
) )
var (
hexToInt = [256]int{
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'A': 10,
'B': 11,
'C': 12,
'D': 13,
'E': 14,
'F': 15,
'a': 10,
'b': 11,
'c': 12,
'd': 13,
'e': 14,
'f': 15,
}
)
func unicodeToRune(code []byte) rune {
sum := 0
for i := 0; i < len(code); i++ {
sum += hexToInt[code[i]] << (uint(len(code)-i-1) * 4)
}
return rune(sum)
}
func decodeEscapeString(s *stream) error {
s.cursor++
RETRY:
switch s.buf[s.cursor] {
case '"':
s.buf[s.cursor] = '"'
case '\\':
s.buf[s.cursor] = '\\'
case '/':
s.buf[s.cursor] = '/'
case 'b':
s.buf[s.cursor] = '\b'
case 'f':
s.buf[s.cursor] = '\f'
case 'n':
s.buf[s.cursor] = '\n'
case 'r':
s.buf[s.cursor] = '\r'
case 't':
s.buf[s.cursor] = '\t'
case 'u':
if s.cursor+5 >= s.length {
if !s.read() {
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
}
code := unicodeToRune(s.buf[s.cursor+1 : s.cursor+5])
unicode := []byte(string(code))
s.buf = append(append(s.buf[:s.cursor-1], unicode...), s.buf[s.cursor+5:]...)
s.cursor--
return nil
case nul:
if !s.read() {
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
goto RETRY
default:
return errUnexpectedEndOfJSON("string", s.totalOffset())
}
s.buf = append(s.buf[:s.cursor-1], s.buf[s.cursor:]...)
s.cursor--
return nil
}
func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error { func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error {
s.skipWhiteSpace() s.skipWhiteSpace()
for { for {

View File

@ -30,6 +30,85 @@ func (d *stringDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, erro
return cursor, nil return cursor, nil
} }
var (
hexToInt = [256]int{
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'A': 10,
'B': 11,
'C': 12,
'D': 13,
'E': 14,
'F': 15,
'a': 10,
'b': 11,
'c': 12,
'd': 13,
'e': 14,
'f': 15,
}
)
func unicodeToRune(code []byte) rune {
sum := 0
for i := 0; i < len(code); i++ {
sum += hexToInt[code[i]] << (uint(len(code)-i-1) * 4)
}
return rune(sum)
}
func decodeEscapeString(s *stream) error {
s.cursor++
RETRY:
switch s.buf[s.cursor] {
case '"':
s.buf[s.cursor] = '"'
case '\\':
s.buf[s.cursor] = '\\'
case '/':
s.buf[s.cursor] = '/'
case 'b':
s.buf[s.cursor] = '\b'
case 'f':
s.buf[s.cursor] = '\f'
case 'n':
s.buf[s.cursor] = '\n'
case 'r':
s.buf[s.cursor] = '\r'
case 't':
s.buf[s.cursor] = '\t'
case 'u':
if s.cursor+5 >= s.length {
if !s.read() {
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
}
code := unicodeToRune(s.buf[s.cursor+1 : s.cursor+5])
unicode := []byte(string(code))
s.buf = append(append(s.buf[:s.cursor-1], unicode...), s.buf[s.cursor+5:]...)
s.cursor--
return nil
case nul:
if !s.read() {
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
goto RETRY
default:
return errUnexpectedEndOfJSON("string", s.totalOffset())
}
s.buf = append(s.buf[:s.cursor-1], s.buf[s.cursor:]...)
s.cursor--
return nil
}
func stringBytes(s *stream) ([]byte, error) { func stringBytes(s *stream) ([]byte, error) {
s.cursor++ s.cursor++
start := s.cursor start := s.cursor
@ -111,6 +190,43 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
switch buf[cursor] { switch buf[cursor] {
case '\\': case '\\':
cursor++ cursor++
switch buf[cursor] {
case '"':
buf[cursor] = '"'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '\\':
buf[cursor] = '\\'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '/':
buf[cursor] = '/'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'b':
buf[cursor] = '\b'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'f':
buf[cursor] = '\f'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'n':
buf[cursor] = '\n'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'r':
buf[cursor] = '\r'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 't':
buf[cursor] = '\t'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'u':
buflen := int64(len(buf))
if cursor+5 >= buflen {
return nil, 0, errUnexpectedEndOfJSON("escaped string", cursor)
}
code := unicodeToRune(buf[cursor+1 : cursor+5])
unicode := []byte(string(code))
buf = append(append(buf[:cursor-1], unicode...), buf[cursor+5:]...)
default:
return nil, 0, errUnexpectedEndOfJSON("escaped string", cursor)
}
continue
case '"': case '"':
literal := buf[start:cursor] literal := buf[start:cursor]
cursor++ cursor++

52
decode_wrapped_string.go Normal file
View File

@ -0,0 +1,52 @@
package json
type wrappedStringDecoder struct {
dec decoder
stringDecoder *stringDecoder
}
func newWrappedStringDecoder(dec decoder) *wrappedStringDecoder {
return &wrappedStringDecoder{
dec: dec,
stringDecoder: newStringDecoder(),
}
}
func (d *wrappedStringDecoder) decodeStream(s *stream, p uintptr) error {
bytes, err := d.stringDecoder.decodeStreamByte(s)
if err != nil {
return err
}
// save current state
buf := s.buf
length := s.length
cursor := s.cursor
// set content in string to stream
bytes = append(bytes, nul)
s.buf = bytes
s.cursor = 0
s.length = int64(len(bytes))
if err := d.dec.decodeStream(s, p); err != nil {
return nil
}
// restore state
s.buf = buf
s.length = length
s.cursor = cursor
return nil
}
func (d *wrappedStringDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error) {
bytes, c, err := d.stringDecoder.decodeByte(buf, cursor)
if err != nil {
return 0, err
}
bytes = append(bytes, nul)
if _, err := d.dec.decode(bytes, 0, p); err != nil {
return 0, err
}
return c, nil
}

View File

@ -3,7 +3,6 @@ package json
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"unsafe" "unsafe"
) )
@ -341,22 +340,6 @@ func (e *Encoder) compileMap(typ *rtype, withLoad, root, withIndent bool) (*opco
return (*opcode)(unsafe.Pointer(header)), nil return (*opcode)(unsafe.Pointer(header)), nil
} }
func (e *Encoder) getTag(field reflect.StructField) string {
return field.Tag.Get("json")
}
func (e *Encoder) isIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" && !field.Anonymous {
// private field
return true
}
tag := e.getTag(field)
if tag == "-" {
return true
}
return false
}
func (e *Encoder) typeToHeaderType(op opType) opType { func (e *Encoder) typeToHeaderType(op opType) opType {
switch op { switch op {
case opInt: case opInt:
@ -433,10 +416,13 @@ func (e *Encoder) typeToFieldType(op opType) opType {
return opStructField return opStructField
} }
func (e *Encoder) optimizeStructHeader(op opType, isOmitEmpty, withIndent bool) opType { func (e *Encoder) optimizeStructHeader(op opType, tag *structTag, withIndent bool) opType {
headType := e.typeToHeaderType(op) headType := e.typeToHeaderType(op)
if isOmitEmpty { switch {
case tag.isOmitEmpty:
headType = headType.headToOmitEmptyHead() headType = headType.headToOmitEmptyHead()
case tag.isString:
headType = headType.headToStringTagHead()
} }
if withIndent { if withIndent {
return headType.toIndent() return headType.toIndent()
@ -444,10 +430,13 @@ func (e *Encoder) optimizeStructHeader(op opType, isOmitEmpty, withIndent bool)
return headType return headType
} }
func (e *Encoder) optimizeStructField(op opType, isOmitEmpty, withIndent bool) opType { func (e *Encoder) optimizeStructField(op opType, tag *structTag, withIndent bool) opType {
fieldType := e.typeToFieldType(op) fieldType := e.typeToFieldType(op)
if isOmitEmpty { switch {
case tag.isOmitEmpty:
fieldType = fieldType.fieldToOmitEmptyField() fieldType = fieldType.fieldToOmitEmptyField()
case tag.isString:
fieldType = fieldType.fieldToStringTagField()
} }
if withIndent { if withIndent {
return fieldType.toIndent() return fieldType.toIndent()
@ -481,25 +470,9 @@ func (e *Encoder) compiledCode(typ *rtype, withIndent bool) *opcode {
return nil return nil
} }
func (e *Encoder) keyNameAndOmitEmptyFromField(field reflect.StructField) (string, bool) { func (e *Encoder) structHeader(fieldCode *structFieldCode, valueCode *opcode, tag *structTag, withIndent bool) *opcode {
keyName := field.Name
tag := e.getTag(field)
opts := strings.Split(tag, ",")
if len(opts) > 0 {
if opts[0] != "" {
keyName = opts[0]
}
}
isOmitEmpty := false
if len(opts) > 1 {
isOmitEmpty = opts[1] == "omitempty"
}
return keyName, isOmitEmpty
}
func (e *Encoder) structHeader(fieldCode *structFieldCode, valueCode *opcode, isOmitEmpty, withIndent bool) *opcode {
fieldCode.indent-- fieldCode.indent--
op := e.optimizeStructHeader(valueCode.op, isOmitEmpty, withIndent) op := e.optimizeStructHeader(valueCode.op, tag, withIndent)
fieldCode.op = op fieldCode.op = op
switch op { switch op {
case opStructFieldHead, case opStructFieldHead,
@ -511,9 +484,9 @@ func (e *Encoder) structHeader(fieldCode *structFieldCode, valueCode *opcode, is
return (*opcode)(unsafe.Pointer(fieldCode)) return (*opcode)(unsafe.Pointer(fieldCode))
} }
func (e *Encoder) structField(fieldCode *structFieldCode, valueCode *opcode, isOmitEmpty, withIndent bool) *opcode { func (e *Encoder) structField(fieldCode *structFieldCode, valueCode *opcode, tag *structTag, withIndent bool) *opcode {
code := (*opcode)(unsafe.Pointer(fieldCode)) code := (*opcode)(unsafe.Pointer(fieldCode))
op := e.optimizeStructField(valueCode.op, isOmitEmpty, withIndent) op := e.optimizeStructField(valueCode.op, tag, withIndent)
fieldCode.op = op fieldCode.op = op
switch op { switch op {
case opStructField, case opStructField,
@ -548,10 +521,10 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
e.indent++ e.indent++
for i := 0; i < fieldNum; i++ { for i := 0; i < fieldNum; i++ {
field := typ.Field(i) field := typ.Field(i)
if e.isIgnoredStructField(field) { if isIgnoredStructField(field) {
continue continue
} }
keyName, isOmitEmpty := e.keyNameAndOmitEmptyFromField(field) tag := structTagFromField(field)
fieldType := type2rtype(field.Type) fieldType := type2rtype(field.Type)
if isPtr && i == 0 { if isPtr && i == 0 {
// head field of pointer structure at top level // head field of pointer structure at top level
@ -579,7 +552,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
f = f.nextField.toStructFieldCode() f = f.nextField.toStructFieldCode()
} }
} }
key := fmt.Sprintf(`"%s":`, keyName) key := fmt.Sprintf(`"%s":`, tag.key)
fieldCode := &structFieldCode{ fieldCode := &structFieldCode{
opcodeHeader: &opcodeHeader{ opcodeHeader: &opcodeHeader{
typ: valueCode.typ, typ: valueCode.typ,
@ -591,13 +564,13 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
offset: field.Offset, offset: field.Offset,
} }
if fieldIdx == 0 { if fieldIdx == 0 {
code = e.structHeader(fieldCode, valueCode, isOmitEmpty, withIndent) code = e.structHeader(fieldCode, valueCode, tag, withIndent)
head = fieldCode head = fieldCode
prevField = fieldCode prevField = fieldCode
} else { } else {
fcode := (*opcode)(unsafe.Pointer(fieldCode)) fcode := (*opcode)(unsafe.Pointer(fieldCode))
code.next = fcode code.next = fcode
code = e.structField(fieldCode, valueCode, isOmitEmpty, withIndent) code = e.structField(fieldCode, valueCode, tag, withIndent)
prevField.nextField = fcode prevField.nextField = fcode
prevField = fieldCode prevField = fieldCode
} }

File diff suppressed because it is too large Load Diff

View File

@ -492,6 +492,77 @@ func Test_MarshalIndent(t *testing.T) {
}) })
} }
type StringTag struct {
BoolStr bool `json:",string"`
IntStr int64 `json:",string"`
UintptrStr uintptr `json:",string"`
StrStr string `json:",string"`
NumberStr json.Number `json:",string"`
}
func TestRoundtripStringTag(t *testing.T) {
tests := []struct {
name string
in StringTag
want string // empty to just test that we roundtrip
}{
{
name: "AllTypes",
in: StringTag{
BoolStr: true,
IntStr: 42,
UintptrStr: 44,
StrStr: "xzbit",
NumberStr: "46",
},
want: `{
"BoolStr": "true",
"IntStr": "42",
"UintptrStr": "44",
"StrStr": "\"xzbit\"",
"NumberStr": "46"
}`,
},
{
// See golang.org/issues/38173.
name: "StringDoubleEscapes",
in: StringTag{
StrStr: "\b\f\n\r\t\"\\",
NumberStr: "0", // just to satisfy the roundtrip
},
want: `{
"BoolStr": "false",
"IntStr": "0",
"UintptrStr": "0",
"StrStr": "\"\\u0008\\u000c\\n\\r\\t\\\"\\\\\"",
"NumberStr": "0"
}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Indent with a tab prefix to make the multi-line string
// literals in the table nicer to read.
got, err := json.MarshalIndent(&test.in, "\t\t\t", "\t")
if err != nil {
t.Fatal(err)
}
if got := string(got); got != test.want {
t.Fatalf(" got: %s\nwant: %s\n", got, test.want)
}
// Verify that it round-trips.
var s2 StringTag
if err := json.Unmarshal(got, &s2); err != nil {
t.Fatalf("Decode: %v", err)
}
if !reflect.DeepEqual(test.in, s2) {
t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2)
}
})
}
}
// byte slices are special even if they're renamed types. // byte slices are special even if they're renamed types.
type renamedByte byte type renamedByte byte
type renamedByteSlice []byte type renamedByteSlice []byte

File diff suppressed because it is too large Load Diff

View File

@ -300,6 +300,15 @@ func (n Number) MarshalJSON() ([]byte, error) {
return []byte(n), nil return []byte(n), nil
} }
func (n *Number) UnmarshalJSON(b []byte) error {
s := string(b)
if _, err := strconv.ParseFloat(s, 64); err != nil {
return err
}
*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
// be used to delay JSON decoding or precompute a JSON encoding. // be used to delay JSON decoding or precompute a JSON encoding.

45
struct_field.go Normal file
View File

@ -0,0 +1,45 @@
package json
import (
"reflect"
"strings"
)
func getTag(field reflect.StructField) string {
return field.Tag.Get("json")
}
func isIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" && !field.Anonymous {
// private field
return true
}
tag := getTag(field)
if tag == "-" {
return true
}
return false
}
type structTag struct {
key string
isOmitEmpty bool
isString bool
}
func structTagFromField(field reflect.StructField) *structTag {
keyName := field.Name
tag := getTag(field)
opts := strings.Split(tag, ",")
if len(opts) > 0 {
if opts[0] != "" {
keyName = opts[0]
}
}
st := &structTag{key: keyName}
if len(opts) > 1 {
st.isOmitEmpty = opts[1] == "omitempty"
st.isString = opts[1] == "string"
}
return st
}