Add Compact/Indent/HTMLEscape/Valid

This commit is contained in:
Masaaki Goshima 2020-08-12 16:54:15 +09:00
parent 1d52cdb354
commit ee13701278
9 changed files with 438 additions and 76 deletions

View File

@ -83,27 +83,7 @@ Currently supported all types
## API
- [ ] `Compact`
- [ ] `HTMLEscape`
- [ ] `Indent`
- [x] `Marshal`
- [x] `MarshalIndent`
- [x] `Unmarshal`
- [ ] `Valid`
- [x] `NewDecoder`
- [x] `(*Decoder).Buffered`
- [x] `(*Decoder).Decode`
- [x] `(*Decoder).DisallowUnknownFields`
- [x] `(*Decoder).InputOffset`
- [x] `(*Decoder).More`
- [x] `(*Decoder).Token`
- [x] `(*Decoder).UseNumber`
- [x] `Delim`
- [x] `(Delim).String`
- [x] `NewEncoder`
- [x] `(*Encoder).Encode`
- [x] `(*Encoder).SetEscapeHTML`
- [x] `(*Encoder).SetIndent`
Implements All APIs
### Error

View File

@ -27,6 +27,7 @@ var floatTable = [256]bool{
'.': true,
'e': true,
'E': true,
'+': true,
}
func floatBytes(s *stream) []byte {
@ -79,8 +80,7 @@ func (d *floatDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, erro
start := cursor
cursor++
for ; cursor < buflen; cursor++ {
tk := int(buf[cursor])
if (int('0') <= tk && tk <= int('9')) || tk == '.' || tk == 'e' || tk == 'E' {
if floatTable[buf[cursor]] {
continue
}
break

View File

@ -65,7 +65,13 @@ func (d *mapDecoder) decodeStream(s *stream, p uintptr) error {
default:
return errExpected("{ character for map value", s.totalOffset())
}
s.skipWhiteSpace()
mapValue := makemap(d.mapType, 0)
if s.buf[s.cursor+1] == '}' {
*(*unsafe.Pointer)(unsafe.Pointer(p)) = mapValue
s.cursor++
return nil
}
for {
s.cursor++
var key interface{}
@ -131,7 +137,13 @@ func (d *mapDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error)
return 0, errExpected("{ character for map value", cursor)
}
cursor++
cursor = skipWhiteSpace(buf, cursor)
mapValue := makemap(d.mapType, 0)
if buf[cursor] == '}' {
*(*unsafe.Pointer)(unsafe.Pointer(p)) = mapValue
cursor++
return cursor, nil
}
for ; cursor < buflen; cursor++ {
var key interface{}
keyCursor, err := d.setKey(buf, cursor, &key)

View File

@ -72,12 +72,22 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
}
return nil
case '[':
s.cursor++
s.skipWhiteSpace()
if s.char() == ']' {
*(*reflect.SliceHeader)(unsafe.Pointer(p)) = reflect.SliceHeader{
Data: uintptr(newArray(d.elemType, 0)),
Len: 0,
Cap: 0,
}
s.cursor++
return nil
}
idx := 0
slice := d.newSlice()
cap := slice.cap
data := slice.data
for {
s.cursor++
if cap <= idx {
src := reflect.SliceHeader{Data: uintptr(data), Len: idx, Cap: cap}
cap *= 2
@ -112,7 +122,6 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
return nil
case ',':
idx++
continue
case nul:
if s.read() {
goto RETRY
@ -127,6 +136,7 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
d.releaseSlice(slice)
goto ERROR
}
s.cursor++
}
case nul:
if s.read() {
@ -162,12 +172,22 @@ func (d *sliceDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error
cursor += 4
return cursor, nil
case '[':
cursor++
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == ']' {
*(*reflect.SliceHeader)(unsafe.Pointer(p)) = reflect.SliceHeader{
Data: uintptr(newArray(d.elemType, 0)),
Len: 0,
Cap: 0,
}
cursor++
return cursor, nil
}
idx := 0
slice := d.newSlice()
cap := slice.cap
data := slice.data
for {
cursor++
if cap <= idx {
src := reflect.SliceHeader{Data: uintptr(data), Len: idx, Cap: cap}
cap *= 2
@ -203,13 +223,13 @@ func (d *sliceDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error
return cursor, nil
case ',':
idx++
continue
default:
slice.cap = cap
slice.data = data
d.releaseSlice(slice)
return 0, errInvalidCharacter(buf[cursor], "slice", cursor)
}
cursor++
}
}
}

View File

@ -16,13 +16,14 @@ func (e *Encoder) compileHead(typ *rtype, withIndent bool) (*opcode, error) {
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
root := true
if typ.Kind() == reflect.Map {
return e.compileMap(typ, false, withIndent)
return e.compileMap(typ, false, root, withIndent)
}
return e.compile(typ, withIndent)
return e.compile(typ, root, withIndent)
}
func (e *Encoder) compile(typ *rtype, withIndent bool) (*opcode, error) {
func (e *Encoder) compile(typ *rtype, root, withIndent bool) (*opcode, error) {
if typ.Implements(marshalJSONType) {
return newOpCode(opMarshalJSON, typ, e.indent, newEndOp(e.indent)), nil
} else if typ.Implements(marshalTextType) {
@ -30,15 +31,17 @@ func (e *Encoder) compile(typ *rtype, withIndent bool) (*opcode, error) {
}
switch typ.Kind() {
case reflect.Ptr:
return e.compilePtr(typ, withIndent)
return e.compilePtr(typ, root, withIndent)
case reflect.Slice:
return e.compileSlice(typ, withIndent)
return e.compileSlice(typ, root, withIndent)
case reflect.Array:
return e.compileArray(typ, withIndent)
return e.compileArray(typ, root, withIndent)
case reflect.Map:
return e.compileMap(typ, true, withIndent)
return e.compileMap(typ, true, root, withIndent)
case reflect.Struct:
return e.compileStruct(typ, withIndent)
return e.compileStruct(typ, root, withIndent)
case reflect.Interface:
return e.compileInterface(typ, root)
case reflect.Int:
return e.compileInt(typ)
case reflect.Int8:
@ -69,8 +72,6 @@ func (e *Encoder) compile(typ *rtype, withIndent bool) (*opcode, error) {
return e.compileString(typ)
case reflect.Bool:
return e.compileBool(typ)
case reflect.Interface:
return e.compileInterface(typ)
}
return nil, &UnsupportedTypeError{Type: rtype2type(typ)}
}
@ -204,8 +205,8 @@ func (e *Encoder) optimizeStructFieldPtrHead(typ *rtype, code *opcode) *opcode {
return code
}
func (e *Encoder) compilePtr(typ *rtype, withIndent bool) (*opcode, error) {
code, err := e.compile(typ.Elem(), withIndent)
func (e *Encoder) compilePtr(typ *rtype, root, withIndent bool) (*opcode, error) {
code, err := e.compile(typ.Elem(), root, withIndent)
if err != nil {
return nil, err
}
@ -268,16 +269,24 @@ func (e *Encoder) compileBool(typ *rtype) (*opcode, error) {
return newOpCode(opBool, typ, e.indent, newEndOp(e.indent)), nil
}
func (e *Encoder) compileInterface(typ *rtype) (*opcode, error) {
return newOpCode(opInterface, typ, e.indent, newEndOp(e.indent)), nil
func (e *Encoder) compileInterface(typ *rtype, root bool) (*opcode, error) {
return (*opcode)(unsafe.Pointer(&interfaceCode{
opcodeHeader: &opcodeHeader{
op: opInterface,
typ: typ,
indent: e.indent,
next: newEndOp(e.indent),
},
root: root,
})), nil
}
func (e *Encoder) compileSlice(typ *rtype, withIndent bool) (*opcode, error) {
func (e *Encoder) compileSlice(typ *rtype, root, withIndent bool) (*opcode, error) {
elem := typ.Elem()
size := elem.Size()
e.indent++
code, err := e.compile(elem, withIndent)
code, err := e.compile(elem, false, withIndent)
e.indent--
if err != nil {
@ -298,8 +307,13 @@ func (e *Encoder) compileSlice(typ *rtype, withIndent bool) (*opcode, error) {
}
end := newOpCode(opSliceEnd, nil, e.indent, newEndOp(e.indent))
if withIndent {
if root {
header.op = opRootSliceHeadIndent
elemCode.op = opRootSliceElemIndent
} else {
header.op = opSliceHeadIndent
elemCode.op = opSliceElemIndent
}
end.op = opSliceEndIndent
}
@ -312,13 +326,13 @@ func (e *Encoder) compileSlice(typ *rtype, withIndent bool) (*opcode, error) {
return (*opcode)(unsafe.Pointer(header)), nil
}
func (e *Encoder) compileArray(typ *rtype, withIndent bool) (*opcode, error) {
func (e *Encoder) compileArray(typ *rtype, root, withIndent bool) (*opcode, error) {
elem := typ.Elem()
alen := typ.Len()
size := elem.Size()
e.indent++
code, err := e.compile(elem, withIndent)
code, err := e.compile(elem, false, withIndent)
e.indent--
if err != nil {
@ -369,18 +383,18 @@ func mapiternext(it unsafe.Pointer)
//go:noescape
func maplen(m unsafe.Pointer) int
func (e *Encoder) compileMap(typ *rtype, withLoad, withIndent bool) (*opcode, error) {
func (e *Encoder) compileMap(typ *rtype, withLoad, root, withIndent bool) (*opcode, error) {
// header => code => value => code => key => code => value => code => end
// ^ |
// |_______________________|
e.indent++
keyType := typ.Key()
keyCode, err := e.compile(keyType, withIndent)
keyCode, err := e.compile(keyType, false, withIndent)
if err != nil {
return nil, err
}
valueType := typ.Elem()
valueCode, err := e.compile(valueType, withIndent)
valueCode, err := e.compile(valueType, false, withIndent)
if err != nil {
return nil, err
}
@ -397,11 +411,19 @@ func (e *Encoder) compileMap(typ *rtype, withLoad, withIndent bool) (*opcode, er
if withIndent {
if header.op == opMapHead {
if root {
header.op = opRootMapHeadIndent
} else {
header.op = opMapHeadIndent
}
} else {
header.op = opMapHeadLoadIndent
}
if root {
key.op = opRootMapKeyIndent
} else {
key.op = opMapKeyIndent
}
value.op = opMapValueIndent
end.op = opMapEndIndent
}
@ -724,7 +746,7 @@ func (e *Encoder) optimizeStructField(op opType, isOmitEmpty, withIndent bool) o
return opStructField
}
func (e *Encoder) compileStruct(typ *rtype, withIndent bool) (*opcode, error) {
func (e *Encoder) compileStruct(typ *rtype, root, withIndent bool) (*opcode, error) {
// header => code => structField => code => end
// ^ |
// |__________|
@ -754,7 +776,7 @@ func (e *Encoder) compileStruct(typ *rtype, withIndent bool) (*opcode, error) {
isOmitEmpty = opts[1] == "omitempty"
}
fieldType := type2rtype(field.Type)
valueCode, err := e.compile(fieldType, withIndent)
valueCode, err := e.compile(fieldType, false, withIndent)
if err != nil {
return nil, err
}

View File

@ -35,7 +35,9 @@ const (
opSliceEnd
opSliceHeadIndent
opRootSliceHeadIndent
opSliceElemIndent
opRootSliceElemIndent
opSliceEndIndent
opArrayHead
@ -50,12 +52,14 @@ const (
opMapHeadLoad
opMapKey
opMapValue
opMapEnd
opMapHeadIndent
opRootMapHeadIndent
opMapHeadLoadIndent
opMapKeyIndent
opRootMapKeyIndent
opMapValueIndent
opMapEnd
opMapEndIndent
// StructFieldHead
@ -310,8 +314,12 @@ func (t opType) String() string {
case opSliceHeadIndent:
return "SLICE_HEAD_INDENT"
case opRootSliceHeadIndent:
return "ROOT_SLICE_HEAD_INDENT"
case opSliceElemIndent:
return "SLICE_ELEM_INDENT"
case opRootSliceElemIndent:
return "ROOT_SLICE_ELEM_INDENT"
case opSliceEndIndent:
return "SLICE_END_INDENT"
@ -341,10 +349,14 @@ func (t opType) String() string {
case opMapHeadIndent:
return "MAP_HEAD_INDENT"
case opRootMapHeadIndent:
return "ROOT_MAP_HEAD_INDENT"
case opMapHeadLoadIndent:
return "MAP_HEAD_LOAD_INDENT"
case opMapKeyIndent:
return "MAP_KEY_INDENT"
case opRootMapKeyIndent:
return "ROOT_MAP_KEY_INDENT"
case opMapValueIndent:
return "MAP_VALUE_INDENT"
case opMapEndIndent:
@ -780,9 +792,9 @@ func (c *opcode) beforeLastCode() *opcode {
switch code.op {
case opArrayElem, opArrayElemIndent:
nextCode = code.toArrayElemCode().end
case opSliceElem, opSliceElemIndent:
case opSliceElem, opSliceElemIndent, opRootSliceElemIndent:
nextCode = code.toSliceElemCode().end
case opMapKey, opMapKeyIndent:
case opMapKey, opMapKeyIndent, opRootMapKeyIndent:
nextCode = code.toMapKeyCode().end
default:
nextCode = code.next
@ -809,13 +821,13 @@ func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode {
code = c.toArrayHeaderCode().copy(codeMap)
case opArrayElem, opArrayElemIndent:
code = c.toArrayElemCode().copy(codeMap)
case opSliceHead, opSliceHeadIndent:
case opSliceHead, opSliceHeadIndent, opRootSliceHeadIndent:
code = c.toSliceHeaderCode().copy(codeMap)
case opSliceElem, opSliceElemIndent:
case opSliceElem, opSliceElemIndent, opRootSliceElemIndent:
code = c.toSliceElemCode().copy(codeMap)
case opMapHead, opMapHeadLoad, opMapHeadIndent, opMapHeadLoadIndent:
case opMapHead, opMapHeadLoad, opMapHeadIndent, opMapHeadLoadIndent, opRootMapHeadIndent:
code = c.toMapHeadCode().copy(codeMap)
case opMapKey, opMapKeyIndent:
case opMapKey, opMapKeyIndent, opRootMapKeyIndent:
code = c.toMapKeyCode().copy(codeMap)
case opMapValue, opMapValueIndent:
code = c.toMapValueCode().copy(codeMap)
@ -1017,9 +1029,9 @@ func (c *opcode) dump() string {
switch code.op {
case opArrayElem, opArrayElemIndent:
code = code.toArrayElemCode().end
case opSliceElem, opSliceElemIndent:
case opSliceElem, opSliceElemIndent, opRootSliceElemIndent:
code = code.toSliceElemCode().end
case opMapKey, opMapKeyIndent:
case opMapKey, opMapKeyIndent, opRootMapKeyIndent:
code = code.toMapKeyCode().end
default:
code = code.next
@ -1060,6 +1072,10 @@ func (c *opcode) toMapValueCode() *mapValueCode {
return (*mapValueCode)(unsafe.Pointer(c))
}
func (c *opcode) toInterfaceCode() *interfaceCode {
return (*interfaceCode)(unsafe.Pointer(c))
}
type sliceHeaderCode struct {
*opcodeHeader
elem *sliceElemCode
@ -1285,6 +1301,27 @@ func (c *mapKeyCode) set(len int, iter unsafe.Pointer) {
c.iter = iter
}
type interfaceCode struct {
*opcodeHeader
root bool
}
func (c *interfaceCode) copy(codeMap map[uintptr]*opcode) *opcode {
if c == nil {
return nil
}
addr := uintptr(unsafe.Pointer(c))
if code, exists := codeMap[addr]; exists {
return code
}
iface := &interfaceCode{}
code := (*opcode)(unsafe.Pointer(iface))
codeMap[addr] = code
iface.opcodeHeader = c.opcodeHeader.copy(codeMap)
return code
}
type mapValueCode struct {
*opcodeHeader
iter unsafe.Pointer

View File

@ -65,19 +65,26 @@ func (e *Encoder) run(code *opcode) error {
e.encodeBool(e.ptrToBool(code.ptr))
code = code.next
case opInterface:
ptr := code.ptr
ifaceCode := code.toInterfaceCode()
ptr := ifaceCode.ptr
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
typ: code.typ,
typ: ifaceCode.typ,
ptr: unsafe.Pointer(ptr),
}))
vv := reflect.ValueOf(v).Interface()
rv := reflect.ValueOf(v)
if rv.IsNil() {
e.encodeNull()
code = ifaceCode.next
break
}
vv := rv.Interface()
header := (*interfaceHeader)(unsafe.Pointer(&vv))
typ := header.typ
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
e.indent = code.indent
c, err := e.compile(typ, e.enabledIndent)
e.indent = ifaceCode.indent
c, err := e.compile(typ, ifaceCode.root, e.enabledIndent)
if err != nil {
return err
}
@ -153,17 +160,37 @@ func (e *Encoder) run(code *opcode) error {
e.encodeNull()
code = headerCode.end.next
} else {
e.encodeBytes([]byte{'[', '\n'})
header := (*reflect.SliceHeader)(unsafe.Pointer(p))
headerCode.elem.set(header)
if header.Len > 0 {
e.encodeBytes([]byte{'[', '\n'})
e.encodeIndent(code.indent + 1)
code = code.next
code.ptr = header.Data
} else {
e.encodeByte('\n')
e.encodeIndent(code.indent)
e.encodeBytes([]byte{']', '\n'})
e.encodeBytes([]byte{'[', ']', '\n'})
code = headerCode.end.next
}
}
case opRootSliceHeadIndent:
p := code.ptr
headerCode := code.toSliceHeaderCode()
if p == 0 {
e.encodeIndent(code.indent)
e.encodeNull()
code = headerCode.end.next
} else {
header := (*reflect.SliceHeader)(unsafe.Pointer(p))
headerCode.elem.set(header)
if header.Len > 0 {
e.encodeBytes([]byte{'[', '\n'})
e.encodeIndent(code.indent + 1)
code = code.next
code.ptr = header.Data
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'[', ']'})
code = headerCode.end.next
}
}
@ -181,6 +208,20 @@ func (e *Encoder) run(code *opcode) error {
e.encodeBytes([]byte{']', '\n'})
code = c.end.next
}
case opRootSliceElemIndent:
c := code.toSliceElemCode()
c.idx++
if c.idx < c.len {
e.encodeBytes([]byte{',', '\n'})
e.encodeIndent(code.indent + 1)
code = code.next
code.ptr = c.data + c.idx*c.size
} else {
e.encodeByte('\n')
e.encodeIndent(code.indent)
e.encodeBytes([]byte{']'})
code = c.end.next
}
case opArrayHead:
p := code.ptr
headerCode := code.toArrayHeaderCode()
@ -287,7 +328,6 @@ func (e *Encoder) run(code *opcode) error {
code = mapHeadCode.end.next
}
}
case opMapKey:
c := code.toMapKeyCode()
c.idx++
@ -315,9 +355,9 @@ func (e *Encoder) run(code *opcode) error {
e.encodeNull()
code = mapHeadCode.end.next
} else {
e.encodeBytes([]byte{'{', '\n'})
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
e.encodeBytes([]byte{'{', '\n'})
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
mapHeadCode.key.set(mlen, iter)
mapHeadCode.value.set(iter)
@ -326,9 +366,58 @@ func (e *Encoder) run(code *opcode) error {
code = code.next
e.encodeIndent(code.indent)
} else {
e.encodeByte('\n')
e.encodeIndent(code.indent - 1)
e.encodeBytes([]byte{'}', '\n'})
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}', '\n'})
code = mapHeadCode.end.next
}
}
case opMapHeadLoadIndent:
ptr := code.ptr
mapHeadCode := code.toMapHeadCode()
if ptr == 0 {
e.encodeIndent(code.indent)
e.encodeNull()
code = mapHeadCode.end.next
} else {
// load pointer
ptr = uintptr(*(*unsafe.Pointer)(unsafe.Pointer(ptr)))
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
e.encodeBytes([]byte{'{', '\n'})
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
mapHeadCode.key.set(mlen, iter)
mapHeadCode.value.set(iter)
key := mapiterkey(iter)
code.next.ptr = uintptr(key)
code = code.next
e.encodeIndent(code.indent)
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}', '\n'})
code = mapHeadCode.end.next
}
}
case opRootMapHeadIndent:
ptr := code.ptr
mapHeadCode := code.toMapHeadCode()
if ptr == 0 {
e.encodeIndent(code.indent)
e.encodeNull()
code = mapHeadCode.end.next
} else {
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
e.encodeBytes([]byte{'{', '\n'})
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
mapHeadCode.key.set(mlen, iter)
mapHeadCode.value.set(iter)
key := mapiterkey(iter)
code.next.ptr = uintptr(key)
code = code.next
e.encodeIndent(code.indent)
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}'})
code = mapHeadCode.end.next
}
}
@ -347,6 +436,21 @@ func (e *Encoder) run(code *opcode) error {
e.encodeBytes([]byte{'}', '\n'})
code = c.end.next
}
case opRootMapKeyIndent:
c := code.toMapKeyCode()
c.idx++
if c.idx < c.len {
e.encodeBytes([]byte{',', '\n'})
e.encodeIndent(code.indent)
key := mapiterkey(c.iter)
c.next.ptr = uintptr(key)
code = c.next
} else {
e.encodeByte('\n')
e.encodeIndent(code.indent - 1)
e.encodeBytes([]byte{'}'})
code = c.end.next
}
case opMapValueIndent:
e.encodeBytes([]byte{':', ' '})
c := code.toMapValueCode()

69
json.go
View File

@ -296,6 +296,10 @@ func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
func (n Number) MarshalJSON() ([]byte, error) {
return []byte(n), nil
}
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
@ -317,3 +321,68 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
*m = append((*m)[0:0], data...)
return nil
}
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
var v interface{}
dec := NewDecoder(bytes.NewBuffer(src))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return err
}
enc := NewEncoder(dst)
enc.SetEscapeHTML(false)
return enc.Encode(v)
}
// Indent appends to dst an indented form of the JSON-encoded src.
// Each element in a JSON object or array begins on a new,
// indented line beginning with prefix followed by one or more
// copies of indent according to the indentation nesting.
// The data appended to dst does not begin with the prefix nor
// any indentation, to make it easier to embed inside other formatted JSON data.
// Although leading space characters (space, tab, carriage return, newline)
// at the beginning of src are dropped, trailing space characters
// at the end of src are preserved and copied to dst.
// For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
var v interface{}
dec := NewDecoder(bytes.NewBuffer(src))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return err
}
enc := NewEncoder(dst)
enc.SetEscapeHTML(false)
enc.SetIndent(prefix, indent)
return enc.Encode(v)
}
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must
// be used.
func HTMLEscape(dst *bytes.Buffer, src []byte) {
var v interface{}
dec := NewDecoder(bytes.NewBuffer(src))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return
}
enc := NewEncoder(dst)
enc.SetEscapeHTML(true)
enc.Encode(v)
}
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
var v interface{}
if err := Unmarshal(data, &v); err != nil {
return false
}
return true
}

118
json_test.go Normal file
View File

@ -0,0 +1,118 @@
package json_test
import (
"bytes"
"testing"
"github.com/goccy/go-json"
)
var validTests = []struct {
data string
ok bool
}{
{`foo`, false},
{`}{`, false},
{`{]`, false},
{`{}`, true},
{`{"foo":"bar"}`, true},
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true},
}
func TestValid(t *testing.T) {
for _, tt := range validTests {
if ok := json.Valid([]byte(tt.data)); ok != tt.ok {
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok)
}
}
}
type example struct {
compact string
indent string
}
var examples = []example{
{`1`, `1`},
{`{}`, `{}`},
{`[]`, `[]`},
{`{"":2}`, "{\n\t\"\": 2\n}"},
{`[3]`, "[\n\t3\n]"},
{`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
{`{"x":1}`, "{\n\t\"x\": 1\n}"},
{ex1, ex1i},
{"{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
}
var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]`
var ex1i = `[
true,
false,
null,
"x",
1,
1.5,
0,
-5e+2
]`
func TestCompact(t *testing.T) {
var buf bytes.Buffer
for _, tt := range examples {
buf.Reset()
t.Log("src = ", tt.compact)
if err := json.Compact(&buf, []byte(tt.compact)); err != nil {
t.Errorf("Compact(%#q): %v", tt.compact, err)
} else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s)
}
buf.Reset()
if err := json.Compact(&buf, []byte(tt.indent)); err != nil {
t.Errorf("Compact(%#q): %v", tt.indent, err)
continue
} else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact)
}
}
}
func TestCompactSeparators(t *testing.T) {
// U+2028 and U+2029 should be escaped inside strings.
// They should not appear outside strings.
tests := []struct {
in, compact string
}{
{"{\"\u2028\": 1}", "{\"\u2028\":1}"},
{"{\"\u2029\" :2}", "{\"\u2029\":2}"},
}
for _, tt := range tests {
var buf bytes.Buffer
if err := json.Compact(&buf, []byte(tt.in)); err != nil {
t.Errorf("Compact(%q): %v", tt.in, err)
} else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact)
}
}
}
func TestIndent(t *testing.T) {
var buf bytes.Buffer
for _, tt := range examples {
buf.Reset()
if err := json.Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
t.Errorf("Indent(%#q): %v", tt.indent, err)
} else if s := buf.String(); s != tt.indent {
t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s)
}
buf.Reset()
if err := json.Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
t.Errorf("Indent(%#q): %v", tt.compact, err)
continue
} else if s := buf.String(); s != tt.indent {
t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent)
}
}
}