diff --git a/feature_adapter.go b/feature_adapter.go index 9dbabb9..eb9af88 100644 --- a/feature_adapter.go +++ b/feature_adapter.go @@ -26,7 +26,7 @@ import ( // Refer to https://godoc.org/encoding/json#Unmarshal for more information func Unmarshal(data []byte, v interface{}) error { data = data[:lastNotSpacePos(data)] - iter := ParseBytes(DEFAULT_CONFIG, data) + iter := ParseBytes(ConfigOfDefault, data) typ := reflect.TypeOf(v) if typ.Kind() != reflect.Ptr { // return non-pointer error @@ -48,7 +48,7 @@ func Unmarshal(data []byte, v interface{}) error { // UnmarshalAny adapts to func UnmarshalAny(data []byte) (Any, error) { data = data[:lastNotSpacePos(data)] - iter := ParseBytes(DEFAULT_CONFIG, data) + iter := ParseBytes(ConfigOfDefault, data) any := iter.ReadAny() if iter.head == iter.tail { iter.loadMore() @@ -74,7 +74,7 @@ func lastNotSpacePos(data []byte) int { func UnmarshalFromString(str string, v interface{}) error { data := []byte(str) data = data[:lastNotSpacePos(data)] - iter := ParseBytes(DEFAULT_CONFIG, data) + iter := ParseBytes(ConfigOfDefault, data) iter.ReadVal(v) if iter.head == iter.tail { iter.loadMore() @@ -91,7 +91,7 @@ func UnmarshalFromString(str string, v interface{}) error { func UnmarshalAnyFromString(str string) (Any, error) { data := []byte(str) data = data[:lastNotSpacePos(data)] - iter := ParseBytes(DEFAULT_CONFIG, data) + iter := ParseBytes(ConfigOfDefault, data) any := iter.ReadAny() if iter.head == iter.tail { iter.loadMore() @@ -110,7 +110,7 @@ func UnmarshalAnyFromString(str string) (Any, error) { // Marshal returns the JSON encoding of v, adapts to json/encoding Marshal API // Refer to https://godoc.org/encoding/json#Marshal for more information func Marshal(v interface{}) ([]byte, error) { - stream := NewStream(DEFAULT_CONFIG, nil, 256) + stream := NewStream(ConfigOfDefault, nil, 256) stream.WriteVal(v) if stream.Error != nil { return nil, stream.Error @@ -133,7 +133,7 @@ func MarshalToString(v interface{}) (string, error) { // Instead of a json/encoding Decoder, an AdaptedDecoder is returned // Refer to https://godoc.org/encoding/json#NewDecoder for more information func NewDecoder(reader io.Reader) *AdaptedDecoder { - iter := Parse(DEFAULT_CONFIG, reader, 512) + iter := Parse(ConfigOfDefault, reader, 512) return &AdaptedDecoder{iter} } diff --git a/feature_any_array.go b/feature_any_array.go index 41c01b3..ef30c81 100644 --- a/feature_any_array.go +++ b/feature_any_array.go @@ -22,7 +22,7 @@ func (any *arrayLazyAny) ValueType() ValueType { func (any *arrayLazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) any.iter = iter } iter.ResetBytes(any.remaining) @@ -287,7 +287,7 @@ func (any *arrayLazyAny) IterateArray() (func() (Any, bool), bool) { // read from buffer iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) any.iter = iter } iter.ResetBytes(remaining) diff --git a/feature_any_float.go b/feature_any_float.go index b393ae9..8a56c00 100644 --- a/feature_any_float.go +++ b/feature_any_float.go @@ -17,7 +17,7 @@ type float64LazyAny struct { func (any *float64LazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) } iter.ResetBytes(any.buf) return iter diff --git a/feature_any_int64.go b/feature_any_int64.go index e77f6a2..a4df9ff 100644 --- a/feature_any_int64.go +++ b/feature_any_int64.go @@ -21,7 +21,7 @@ func (any *int64LazyAny) ValueType() ValueType { func (any *int64LazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) } iter.ResetBytes(any.buf) return iter diff --git a/feature_any_object.go b/feature_any_object.go index adb7e66..99979c8 100644 --- a/feature_any_object.go +++ b/feature_any_object.go @@ -22,7 +22,7 @@ func (any *objectLazyAny) ValueType() ValueType { func (any *objectLazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) any.iter = iter } iter.ResetBytes(any.remaining) @@ -308,7 +308,7 @@ func (any *objectLazyAny) IterateObject() (func() (string, Any, bool), bool) { // read from buffer iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) any.iter = iter } iter.ResetBytes(remaining) diff --git a/feature_any_string.go b/feature_any_string.go index b516a69..c596de4 100644 --- a/feature_any_string.go +++ b/feature_any_string.go @@ -20,7 +20,7 @@ func (any *stringLazyAny) ValueType() ValueType { func (any *stringLazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) any.iter = iter } iter.ResetBytes(any.buf) diff --git a/feature_any_uint64.go b/feature_any_uint64.go index bd6d61a..7972ac9 100644 --- a/feature_any_uint64.go +++ b/feature_any_uint64.go @@ -21,7 +21,7 @@ func (any *uint64LazyAny) ValueType() ValueType { func (any *uint64LazyAny) Parse() *Iterator { iter := any.iter if iter == nil { - iter = NewIterator(DEFAULT_CONFIG) + iter = NewIterator(ConfigOfDefault) } iter.ResetBytes(any.buf) return iter diff --git a/feature_config.go b/feature_config.go index a84e86b..ed70d75 100644 --- a/feature_config.go +++ b/feature_config.go @@ -11,6 +11,7 @@ type Config struct { IndentionStep int MarshalFloatWith6Digits bool SupportUnexportedStructFields bool + EscapeHtml bool } type frozenConfig struct { @@ -20,7 +21,12 @@ type frozenConfig struct { extensions []ExtensionFunc } -var DEFAULT_CONFIG = Config{}.Froze() +var ConfigOfDefault = Config{}.Froze() + +// Trying to be 100% compatible with standard library behavior +var ConfigCompatibleWithStandardLibrary = Config{ + EscapeHtml: true, +}.Froze() func (cfg Config) Froze() *frozenConfig { frozenConfig := &frozenConfig{ @@ -34,16 +40,19 @@ func (cfg Config) Froze() *frozenConfig { if cfg.SupportUnexportedStructFields { frozenConfig.supportUnexportedStructFields() } + if cfg.EscapeHtml { + frozenConfig.escapeHtml() + } return frozenConfig } // RegisterExtension can register a custom extension -func (cfg *frozenConfig) RegisterExtension(extension ExtensionFunc) { +func (cfg *frozenConfig) registerExtension(extension ExtensionFunc) { cfg.extensions = append(cfg.extensions, extension) } func (cfg *frozenConfig) supportUnexportedStructFields() { - cfg.RegisterExtension(func(type_ reflect.Type, field *reflect.StructField) ([]string, EncoderFunc, DecoderFunc) { + cfg.registerExtension(func(type_ reflect.Type, field *reflect.StructField) ([]string, EncoderFunc, DecoderFunc) { return []string{field.Name}, nil, nil }) } @@ -62,6 +71,14 @@ func (cfg *frozenConfig) marshalFloatWith6Digits() { }}) } +func (cfg *frozenConfig) escapeHtml() { + // for better performance + cfg.addEncoderToCache(reflect.TypeOf((*string)(nil)).Elem(), &funcEncoder{func(ptr unsafe.Pointer, stream *Stream) { + val := *((*string)(ptr)) + stream.WriteStringWithHtmlEscaped(val) + }}) +} + func (cfg *frozenConfig) addDecoderToCache(cacheKey reflect.Type, decoder Decoder) { done := false for !done { diff --git a/feature_stream.go b/feature_stream.go index 7ad767a..b80ba42 100644 --- a/feature_stream.go +++ b/feature_stream.go @@ -197,64 +197,6 @@ func (b *Stream) WriteRaw(s string) { b.n += n } -func (stream *Stream) WriteString(s string) { - stream.ensure(32) - valLen := len(s) - toWriteLen := valLen - bufLengthMinusTwo := len(stream.buf) - 2 // make room for the quotes - if stream.n+toWriteLen > bufLengthMinusTwo { - toWriteLen = bufLengthMinusTwo - stream.n - } - n := stream.n - stream.buf[n] = '"' - n++ - // write string, the fast path, without utf8 and escape support - i := 0 - for ; i < toWriteLen; i++ { - c := s[i] - if c > 31 && c != '"' && c != '\\' { - stream.buf[n] = c - n++ - } else { - break - } - } - if i == valLen { - stream.buf[n] = '"' - n++ - stream.n = n - return - } - stream.n = n - // for the remaining parts, we process them char by char - stream.writeStringSlowPath(s, i, valLen) - stream.writeByte('"') -} - -func (stream *Stream) writeStringSlowPath(s string, i int, valLen int) { - for ; i < valLen; i++ { - c := s[i] - switch c { - case '"': - stream.writeTwoBytes('\\', '"') - case '\\': - stream.writeTwoBytes('\\', '\\') - case '\b': - stream.writeTwoBytes('\\', 'b') - case '\f': - stream.writeTwoBytes('\\', 'f') - case '\n': - stream.writeTwoBytes('\\', 'n') - case '\r': - stream.writeTwoBytes('\\', 'r') - case '\t': - stream.writeTwoBytes('\\', 't') - default: - stream.writeByte(c) - } - } -} - func (stream *Stream) WriteNil() { stream.writeFourBytes('n', 'u', 'l', 'l') } diff --git a/feature_stream_string.go b/feature_stream_string.go new file mode 100644 index 0000000..eaa6fe7 --- /dev/null +++ b/feature_stream_string.go @@ -0,0 +1,367 @@ +package jsoniter + +import "unicode/utf8" + +// htmlSafeSet holds the value true if the ASCII character with the given +// array position can be safely represented inside a JSON string, embedded +// inside of HTML