mirror of
https://github.com/json-iterator/go.git
synced 2024-11-24 08:22:14 +02:00
fix #191 do not always assume the object field is simple string
This commit is contained in:
parent
fbd210edfc
commit
f1258b01aa
@ -110,9 +110,7 @@ type Encoder struct {
|
|||||||
// Encode encode interface{} as JSON to io.Writer
|
// Encode encode interface{} as JSON to io.Writer
|
||||||
func (adapter *Encoder) Encode(val interface{}) error {
|
func (adapter *Encoder) Encode(val interface{}) error {
|
||||||
adapter.stream.WriteVal(val)
|
adapter.stream.WriteVal(val)
|
||||||
if adapter.stream.cfg.addNewline {
|
adapter.stream.WriteRaw("\n")
|
||||||
adapter.stream.WriteRaw("\n")
|
|
||||||
}
|
|
||||||
adapter.stream.Flush()
|
adapter.stream.Flush()
|
||||||
return adapter.stream.Error
|
return adapter.stream.Error
|
||||||
}
|
}
|
||||||
|
@ -12,26 +12,26 @@ import (
|
|||||||
// Config customize how the API should behave.
|
// Config customize how the API should behave.
|
||||||
// The API is created from Config by Froze.
|
// The API is created from Config by Froze.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
IndentionStep int
|
IndentionStep int
|
||||||
MarshalFloatWith6Digits bool
|
MarshalFloatWith6Digits bool
|
||||||
EscapeHTML bool
|
EscapeHTML bool
|
||||||
SortMapKeys bool
|
SortMapKeys bool
|
||||||
UseNumber bool
|
UseNumber bool
|
||||||
TagKey string
|
TagKey string
|
||||||
ValidateJsonRawMessage bool
|
ValidateJsonRawMessage bool
|
||||||
AddNewline bool
|
ObjectFieldMustBeSimpleString bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type frozenConfig struct {
|
type frozenConfig struct {
|
||||||
configBeforeFrozen Config
|
configBeforeFrozen Config
|
||||||
sortMapKeys bool
|
sortMapKeys bool
|
||||||
addNewline bool
|
indentionStep int
|
||||||
indentionStep int
|
objectFieldMustBeSimpleString bool
|
||||||
decoderCache unsafe.Pointer
|
decoderCache unsafe.Pointer
|
||||||
encoderCache unsafe.Pointer
|
encoderCache unsafe.Pointer
|
||||||
extensions []Extension
|
extensions []Extension
|
||||||
streamPool chan *Stream
|
streamPool chan *Stream
|
||||||
iteratorPool chan *Iterator
|
iteratorPool chan *Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
// API the public interface of this package.
|
// API the public interface of this package.
|
||||||
@ -60,24 +60,24 @@ var ConfigCompatibleWithStandardLibrary = Config{
|
|||||||
EscapeHTML: true,
|
EscapeHTML: true,
|
||||||
SortMapKeys: true,
|
SortMapKeys: true,
|
||||||
ValidateJsonRawMessage: true,
|
ValidateJsonRawMessage: true,
|
||||||
AddNewline: true,
|
|
||||||
}.Froze()
|
}.Froze()
|
||||||
|
|
||||||
// ConfigFastest marshals float with only 6 digits precision
|
// ConfigFastest marshals float with only 6 digits precision
|
||||||
var ConfigFastest = Config{
|
var ConfigFastest = Config{
|
||||||
EscapeHTML: false,
|
EscapeHTML: false,
|
||||||
MarshalFloatWith6Digits: true,
|
MarshalFloatWith6Digits: true, // will lose precession
|
||||||
|
ObjectFieldMustBeSimpleString: true, // do not unescape object field
|
||||||
}.Froze()
|
}.Froze()
|
||||||
|
|
||||||
// Froze forge API from config
|
// Froze forge API from config
|
||||||
func (cfg Config) Froze() API {
|
func (cfg Config) Froze() API {
|
||||||
// TODO: cache frozen config
|
// TODO: cache frozen config
|
||||||
frozenConfig := &frozenConfig{
|
frozenConfig := &frozenConfig{
|
||||||
sortMapKeys: cfg.SortMapKeys,
|
sortMapKeys: cfg.SortMapKeys,
|
||||||
indentionStep: cfg.IndentionStep,
|
indentionStep: cfg.IndentionStep,
|
||||||
addNewline: cfg.AddNewline,
|
objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString,
|
||||||
streamPool: make(chan *Stream, 16),
|
streamPool: make(chan *Stream, 16),
|
||||||
iteratorPool: make(chan *Iterator, 16),
|
iteratorPool: make(chan *Iterator, 16),
|
||||||
}
|
}
|
||||||
atomic.StorePointer(&frozenConfig.decoderCache, unsafe.Pointer(&map[string]ValDecoder{}))
|
atomic.StorePointer(&frozenConfig.decoderCache, unsafe.Pointer(&map[string]ValDecoder{}))
|
||||||
atomic.StorePointer(&frozenConfig.encoderCache, unsafe.Pointer(&map[string]ValEncoder{}))
|
atomic.StorePointer(&frozenConfig.encoderCache, unsafe.Pointer(&map[string]ValEncoder{}))
|
||||||
|
@ -19,7 +19,16 @@ func (iter *Iterator) ReadObject() (ret string) {
|
|||||||
c = iter.nextToken()
|
c = iter.nextToken()
|
||||||
if c == '"' {
|
if c == '"' {
|
||||||
iter.unreadByte()
|
iter.unreadByte()
|
||||||
return string(iter.readObjectFieldAsBytes())
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
|
return string(iter.readObjectFieldAsBytes())
|
||||||
|
} else {
|
||||||
|
field := iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c == '}' {
|
if c == '}' {
|
||||||
return "" // end of object
|
return "" // end of object
|
||||||
@ -27,7 +36,16 @@ func (iter *Iterator) ReadObject() (ret string) {
|
|||||||
iter.ReportError("ReadObject", `expect " after {, but found `+string([]byte{c}))
|
iter.ReportError("ReadObject", `expect " after {, but found `+string([]byte{c}))
|
||||||
return
|
return
|
||||||
case ',':
|
case ',':
|
||||||
return string(iter.readObjectFieldAsBytes())
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
|
return string(iter.readObjectFieldAsBytes())
|
||||||
|
} else {
|
||||||
|
field := iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
case '}':
|
case '}':
|
||||||
return "" // end of object
|
return "" // end of object
|
||||||
default:
|
default:
|
||||||
@ -44,17 +62,34 @@ func (iter *Iterator) readFieldHash() int32 {
|
|||||||
for i := iter.head; i < iter.tail; i++ {
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
// require ascii string and no escape
|
// require ascii string and no escape
|
||||||
b := iter.buf[i]
|
b := iter.buf[i]
|
||||||
if 'A' <= b && b <= 'Z' {
|
if !iter.cfg.objectFieldMustBeSimpleString && b == '\\' {
|
||||||
b += 'a' - 'A'
|
iter.head = i
|
||||||
|
for _, b := range iter.readStringSlowPath() {
|
||||||
|
if 'A' <= b && b <= 'Z' {
|
||||||
|
b += 'a' - 'A'
|
||||||
|
}
|
||||||
|
hash ^= int64(b)
|
||||||
|
hash *= 0x1000193
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int32(hash)
|
||||||
}
|
}
|
||||||
if b == '"' {
|
if b == '"' {
|
||||||
iter.head = i + 1
|
iter.head = i + 1
|
||||||
c = iter.nextToken()
|
c = iter.nextToken()
|
||||||
if c != ':' {
|
if c != ':' {
|
||||||
iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
|
iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
return int32(hash)
|
return int32(hash)
|
||||||
}
|
}
|
||||||
|
if 'A' <= b && b <= 'Z' {
|
||||||
|
b += 'a' - 'A'
|
||||||
|
}
|
||||||
hash ^= int64(b)
|
hash ^= int64(b)
|
||||||
hash *= 0x1000193
|
hash *= 0x1000193
|
||||||
}
|
}
|
||||||
@ -80,18 +115,38 @@ func calcHash(str string) int32 {
|
|||||||
// ReadObjectCB read object with callback, the key is ascii only and field name not copied
|
// ReadObjectCB read object with callback, the key is ascii only and field name not copied
|
||||||
func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool {
|
func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool {
|
||||||
c := iter.nextToken()
|
c := iter.nextToken()
|
||||||
|
var fieldBytes []byte
|
||||||
|
var field string
|
||||||
if c == '{' {
|
if c == '{' {
|
||||||
c = iter.nextToken()
|
c = iter.nextToken()
|
||||||
if c == '"' {
|
if c == '"' {
|
||||||
iter.unreadByte()
|
iter.unreadByte()
|
||||||
field := iter.readObjectFieldAsBytes()
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
if !callback(iter, *(*string)(unsafe.Pointer(&field))) {
|
fieldBytes = iter.readObjectFieldAsBytes()
|
||||||
|
field = *(*string)(unsafe.Pointer(&fieldBytes))
|
||||||
|
} else {
|
||||||
|
field = iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c = iter.nextToken()
|
c = iter.nextToken()
|
||||||
for c == ',' {
|
for c == ',' {
|
||||||
field = iter.readObjectFieldAsBytes()
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
if !callback(iter, *(*string)(unsafe.Pointer(&field))) {
|
fieldBytes = iter.readObjectFieldAsBytes()
|
||||||
|
field = *(*string)(unsafe.Pointer(&fieldBytes))
|
||||||
|
} else {
|
||||||
|
field = iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c = iter.nextToken()
|
c = iter.nextToken()
|
||||||
|
@ -427,8 +427,18 @@ func (decoder *generalStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator)
|
|||||||
if !iter.readObjectStart() {
|
if !iter.readObjectStart() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fieldBytes := iter.readObjectFieldAsBytes()
|
var fieldBytes []byte
|
||||||
field := *(*string)(unsafe.Pointer(&fieldBytes))
|
var field string
|
||||||
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
|
fieldBytes = iter.readObjectFieldAsBytes()
|
||||||
|
field = *(*string)(unsafe.Pointer(&fieldBytes))
|
||||||
|
} else {
|
||||||
|
field = iter.ReadString()
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
}
|
||||||
fieldDecoder := decoder.fields[strings.ToLower(field)]
|
fieldDecoder := decoder.fields[strings.ToLower(field)]
|
||||||
if fieldDecoder == nil {
|
if fieldDecoder == nil {
|
||||||
iter.Skip()
|
iter.Skip()
|
||||||
@ -436,8 +446,16 @@ func (decoder *generalStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator)
|
|||||||
fieldDecoder.Decode(ptr, iter)
|
fieldDecoder.Decode(ptr, iter)
|
||||||
}
|
}
|
||||||
for iter.nextToken() == ',' {
|
for iter.nextToken() == ',' {
|
||||||
fieldBytes = iter.readObjectFieldAsBytes()
|
if iter.cfg.objectFieldMustBeSimpleString {
|
||||||
field = *(*string)(unsafe.Pointer(&fieldBytes))
|
fieldBytes := iter.readObjectFieldAsBytes()
|
||||||
|
field = *(*string)(unsafe.Pointer(&fieldBytes))
|
||||||
|
} else {
|
||||||
|
field = iter.ReadString()
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
}
|
||||||
fieldDecoder = decoder.fields[strings.ToLower(field)]
|
fieldDecoder = decoder.fields[strings.ToLower(field)]
|
||||||
if fieldDecoder == nil {
|
if fieldDecoder == nil {
|
||||||
iter.Skip()
|
iter.Skip()
|
||||||
|
@ -22,7 +22,7 @@ func Test_new_encoder(t *testing.T) {
|
|||||||
encoder2 := NewEncoder(buf2)
|
encoder2 := NewEncoder(buf2)
|
||||||
encoder2.SetEscapeHTML(false)
|
encoder2.SetEscapeHTML(false)
|
||||||
encoder2.Encode([]int{1})
|
encoder2.Encode([]int{1})
|
||||||
should.Equal("[1]", buf2.String())
|
should.Equal("[1]\n", buf2.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_string_encode_with_std_without_html_escape(t *testing.T) {
|
func Test_string_encode_with_std_without_html_escape(t *testing.T) {
|
||||||
|
@ -328,3 +328,15 @@ func Test_decode_nested(t *testing.T) {
|
|||||||
t.Fatal(slice[2])
|
t.Fatal(slice[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_decode_field_with_escape(t *testing.T) {
|
||||||
|
should := require.New(t)
|
||||||
|
type TestObject struct {
|
||||||
|
Field1 string
|
||||||
|
}
|
||||||
|
var obj TestObject
|
||||||
|
should.Nil(ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(`{"Field\"1":"hello"}`), &obj))
|
||||||
|
should.Equal("", obj.Field1)
|
||||||
|
should.Nil(ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(`{"\u0046ield1":"hello"}`), &obj))
|
||||||
|
should.Equal("hello", obj.Field1)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user