Enable switch map processing at runtime

This commit is contained in:
Masaaki Goshima 2020-09-16 18:15:47 +09:00
parent 898d58b8b8
commit aaea586778
8 changed files with 962 additions and 1120 deletions

View File

@ -238,13 +238,6 @@ func (t opType) fieldToStringTagField() opType {
{"RootMapKey", "RootMapKeyIndent", "MapKey"},
{"MapValue", "MapValueIndent", "MapValue"},
{"MapEnd", "MapEndIndent", "Op"},
{"SortedMapHead", "SortedMapHeadIndent", "MapHead"},
{"SortedMapHeadLoad", "SortedMapHeadLoadIndent", "MapHead"},
{"SortedRootMapHead", "SortedRootMapHeadIndent", "MapHead"},
{"SortedMapKey", "SortedMapKeyIndent", "MapKey"},
{"SortedRootMapKey", "SortedRootMapKeyIndent", "MapKey"},
{"SortedMapValue", "SortedMapValueIndent", "MapValue"},
{"SortedMapEnd", "SortedMapEndIndent", "MapEnd"},
{"StructFieldHead", "StructFieldHeadIndent", "StructField"},
{"StructFieldHeadOmitEmpty", "StructFieldHeadOmitEmptyIndent", "StructField"},
{"StructFieldHeadStringTag", "StructFieldHeadStringTagIndent", "StructField"},

View File

@ -18,6 +18,7 @@ type Encoder struct {
buf []byte
enabledIndent bool
enabledHTMLEscape bool
unorderedMap bool
prefix []byte
indentStr []byte
structTypeToCompiledCode map[uintptr]*compiledCode
@ -88,6 +89,16 @@ func NewEncoder(w io.Writer) *Encoder {
//
// See the documentation for Marshal for details about the conversion of Go values to JSON.
func (e *Encoder) Encode(v interface{}) error {
return e.EncodeWithOption(v)
}
// EncodeWithOption call Encode with EncodeOption.
func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error {
for _, opt := range opts {
if err := opt(e); err != nil {
return err
}
}
if err := e.encode(v); err != nil {
return err
}
@ -126,6 +137,7 @@ func (e *Encoder) reset() {
e.buf = e.buf[:0]
e.enabledHTMLEscape = true
e.enabledIndent = false
e.unorderedMap = false
}
func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {

View File

@ -452,13 +452,13 @@ func (e *Encoder) typeToHeaderType(op opType) opType {
return opStructFieldHeadString
case opBool:
return opStructFieldHeadBool
case opMapHead, opSortedMapHead:
case opMapHead:
return opStructFieldHeadMap
case opMapHeadLoad, opSortedMapHeadLoad:
case opMapHeadLoad:
return opStructFieldHeadMapLoad
case opMapHeadIndent, opSortedMapHeadIndent:
case opMapHeadIndent:
return opStructFieldHeadMapIndent
case opMapHeadLoadIndent, opSortedMapHeadLoadIndent:
case opMapHeadLoadIndent:
return opStructFieldHeadMapLoadIndent
case opArrayHead:
return opStructFieldHeadArray
@ -510,13 +510,13 @@ func (e *Encoder) typeToFieldType(op opType) opType {
return opStructFieldString
case opBool:
return opStructFieldBool
case opMapHead, opSortedMapHead:
case opMapHead:
return opStructFieldMap
case opMapHeadLoad, opSortedMapHeadLoad:
case opMapHeadLoad:
return opStructFieldMapLoad
case opMapHeadIndent, opSortedMapHeadIndent:
case opMapHeadIndent:
return opStructFieldMapIndent
case opMapHeadLoadIndent, opSortedMapHeadLoadIndent:
case opMapHeadLoadIndent:
return opStructFieldMapLoadIndent
case opArrayHead:
return opStructFieldArray

View File

@ -389,9 +389,9 @@ func newArrayElemCode(ctx *encodeCompileContext, head *opcode, length int, size
func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode {
var op opType
if withLoad {
op = opSortedMapHeadLoad
op = opMapHeadLoad
} else {
op = opSortedMapHead
op = opMapHead
}
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
@ -414,7 +414,7 @@ func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *opcode {
func newMapKeyCode(ctx *encodeCompileContext, head *opcode) *opcode {
return &opcode{
op: opSortedMapKey,
op: opMapKey,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
elemIdx: head.elemIdx,
@ -426,7 +426,7 @@ func newMapKeyCode(ctx *encodeCompileContext, head *opcode) *opcode {
func newMapValueCode(ctx *encodeCompileContext, head *opcode) *opcode {
return &opcode{
op: opSortedMapValue,
op: opMapValue,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
elemIdx: head.elemIdx,
@ -443,7 +443,7 @@ func newMapEndCode(ctx *encodeCompileContext, head *opcode) *opcode {
ctx.incPtrIndex()
idx := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opSortedMapEnd,
op: opMapEnd,
displayIdx: ctx.opcodeIndex,
idx: idx,
length: head.length,

File diff suppressed because it is too large Load Diff

View File

@ -377,14 +377,18 @@ func Test_Marshal(t *testing.T) {
})
t.Run("map", func(t *testing.T) {
t.Run("map[string]int", func(t *testing.T) {
bytes, err := json.Marshal(map[string]int{
v := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
})
}
bytes, err := json.Marshal(v)
assertErr(t, err)
assertEq(t, "map", `{"a":1,"b":2,"c":3,"d":4}`, string(bytes))
b, err := json.MarshalWithOption(v, json.UnorderedMap())
assertErr(t, err)
assertEq(t, "unordered map", len(`{"a":1,"b":2,"c":3,"d":4}`), len(string(b)))
})
t.Run("map[string]interface{}", func(t *testing.T) {
type T struct {

View File

@ -426,7 +426,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
e.encodeBytes([]byte{']', '\n'})
code = code.end.next
}
case opSortedMapHead:
case opMapHead:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
@ -440,11 +440,13 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
if !e.unorderedMap {
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
}
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
@ -453,7 +455,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
code = code.end.next
}
}
case opSortedMapHeadLoad:
case opMapHeadLoad:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
@ -471,23 +473,38 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
store(ctxptr, code.mapIter, uintptr(iter))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
if !e.unorderedMap {
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
}
code = code.next
} else {
e.encodeByte('}')
code = code.end.next
}
}
case opSortedMapKey:
case opMapKey:
idx := load(ctxptr, code.elemIdx)
length := load(ctxptr, code.length)
idx++
if e.unorderedMap {
if idx < length {
e.encodeByte(',')
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
store(ctxptr, code.elemIdx, idx)
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
e.encodeByte('}')
code = code.end.next
}
} else {
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
idx++
if idx < length {
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
store(ctxptr, code.elemIdx, idx)
@ -497,15 +514,21 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
} else {
code = code.end
}
case opSortedMapValue:
}
case opMapValue:
if e.unorderedMap {
e.encodeByte(':')
} else {
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
}
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
value := mapitervalue(iter)
store(ctxptr, code.next.idx, uintptr(value))
mapiternext(iter)
code = code.next
case opSortedMapEnd:
case opMapEnd:
// this operation only used by sorted map.
length := int(load(ctxptr, code.length))
type mapKV struct {
key string
@ -545,204 +568,6 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
e.buf = e.buf[:pos[0]]
e.buf = append(e.buf, buf...)
code = code.next
case opMapHead:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
code = code.end.next
} else {
e.encodeByte('{')
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
ctx.keepRefs = append(ctx.keepRefs, iter)
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
e.encodeByte('}')
code = code.end.next
}
}
case opMapHeadLoad:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
code = code.end.next
} else {
// load pointer
ptr = uintptr(*(*unsafe.Pointer)(unsafe.Pointer(ptr)))
e.encodeByte('{')
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
ctx.keepRefs = append(ctx.keepRefs, iter)
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
e.encodeByte('}')
code = code.end.next
}
}
case opMapKey:
idx := load(ctxptr, code.elemIdx)
length := load(ctxptr, code.length)
idx++
if idx < length {
e.encodeByte(',')
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
store(ctxptr, code.elemIdx, idx)
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
e.encodeByte('}')
code = code.end.next
}
case opMapValue:
e.encodeByte(':')
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
value := mapitervalue(iter)
store(ctxptr, code.next.idx, uintptr(value))
mapiternext(iter)
code = code.next
case opSortedMapHeadIndent:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
code = code.end.next
} else {
mlen := maplen(unsafe.Pointer(ptr))
if mlen > 0 {
e.encodeBytes([]byte{'{', '\n'})
iter := mapiterinit(code.typ, unsafe.Pointer(ptr))
ctx.keepRefs = append(ctx.keepRefs, iter)
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}'})
code = code.end.next
}
}
case opSortedMapHeadLoadIndent:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
e.encodeNull()
code = code.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))
ctx.keepRefs = append(ctx.keepRefs, iter)
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
code = code.next
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}'})
code = code.end.next
}
}
case opSortedMapKeyIndent:
idx := load(ctxptr, code.elemIdx)
length := load(ctxptr, code.length)
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
idx++
if idx < length {
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
store(ctxptr, code.elemIdx, idx)
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
code = code.end
}
case opSortedMapValueIndent:
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
value := mapitervalue(iter)
store(ctxptr, code.next.idx, uintptr(value))
mapiternext(iter)
code = code.next
case opSortedMapEndIndent:
length := int(load(ctxptr, code.length))
type mapKV struct {
key string
value string
}
kvs := make([]mapKV, 0, length)
pos := *(*[]int)(unsafe.Pointer(load(ctxptr, code.mapPos)))
for i := 0; i < length; i++ {
startKey := pos[i*2]
startValue := pos[i*2+1]
var endValue int
if i+1 < length {
endValue = pos[i*2+2]
} else {
endValue = len(e.buf)
}
kvs = append(kvs, mapKV{
key: string(e.buf[startKey:startValue]),
value: string(e.buf[startValue:endValue]),
})
}
fmt.Println("kvs = ", kvs)
sort.Slice(kvs, func(i, j int) bool {
return kvs[i].key < kvs[j].key
})
buf := e.buf[pos[0]:]
buf = buf[:0]
for idx, kv := range kvs {
if idx != 0 {
buf = append(buf, []byte{',', '\n'}...)
}
buf = append(buf, e.prefix...)
buf = append(buf, bytes.Repeat(e.indentStr, code.indent+1)...)
buf = append(buf, []byte(kv.key)...)
buf = append(buf, []byte{':', ' '}...)
buf = append(buf, []byte(kv.value)...)
}
buf = append(buf, '\n')
buf = append(buf, e.prefix...)
buf = append(buf, bytes.Repeat(e.indentStr, code.indent)...)
buf = append(buf, '}')
e.buf = e.buf[:pos[0]]
e.buf = append(e.buf, buf...)
code = code.next
case opMapHeadIndent:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
@ -758,10 +583,20 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
store(ctxptr, code.elemIdx, 0)
store(ctxptr, code.length, uintptr(mlen))
store(ctxptr, code.mapIter, uintptr(iter))
if !e.unorderedMap {
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
} else {
e.encodeIndent(code.next.indent)
}
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
e.encodeIndent(code.indent)
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}'})
@ -787,8 +622,18 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
store(ctxptr, code.mapIter, uintptr(iter))
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
if !e.unorderedMap {
pos := make([]int, 0, mlen)
pos = append(pos, len(e.buf))
posPtr := unsafe.Pointer(&pos)
ctx.keepRefs = append(ctx.keepRefs, posPtr)
store(ctxptr, code.end.mapPos, uintptr(posPtr))
} else {
e.encodeIndent(code.next.indent)
}
code = code.next
e.encodeIndent(code.indent)
} else {
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '}'})
@ -824,6 +669,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
idx := load(ctxptr, code.elemIdx)
length := load(ctxptr, code.length)
idx++
if e.unorderedMap {
if idx < length {
e.encodeBytes([]byte{',', '\n'})
e.encodeIndent(code.indent)
@ -838,6 +684,19 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
e.encodeByte('}')
code = code.end.next
}
} else {
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
if idx < length {
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
store(ctxptr, code.elemIdx, idx)
key := mapiterkey(iter)
store(ctxptr, code.next.idx, uintptr(key))
code = code.next
} else {
code = code.end
}
}
case opRootMapKeyIndent:
idx := load(ctxptr, code.elemIdx)
length := load(ctxptr, code.length)
@ -857,12 +716,64 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
code = code.end.next
}
case opMapValueIndent:
if e.unorderedMap {
e.encodeBytes([]byte{':', ' '})
} else {
posPtr := (*[]int)(unsafe.Pointer(load(ctxptr, code.end.mapPos)))
*posPtr = append(*posPtr, len(e.buf))
}
iter := unsafe.Pointer(load(ctxptr, code.mapIter))
value := mapitervalue(iter)
store(ctxptr, code.next.idx, uintptr(value))
mapiternext(iter)
code = code.next
case opMapEndIndent:
// this operation only used by sorted map
length := int(load(ctxptr, code.length))
type mapKV struct {
key string
value string
}
kvs := make([]mapKV, 0, length)
pos := *(*[]int)(unsafe.Pointer(load(ctxptr, code.mapPos)))
for i := 0; i < length; i++ {
startKey := pos[i*2]
startValue := pos[i*2+1]
var endValue int
if i+1 < length {
endValue = pos[i*2+2]
} else {
endValue = len(e.buf)
}
kvs = append(kvs, mapKV{
key: string(e.buf[startKey:startValue]),
value: string(e.buf[startValue:endValue]),
})
}
sort.Slice(kvs, func(i, j int) bool {
return kvs[i].key < kvs[j].key
})
buf := e.buf[pos[0]:]
buf = buf[:0]
for idx, kv := range kvs {
if idx != 0 {
buf = append(buf, []byte{',', '\n'}...)
}
buf = append(buf, e.prefix...)
buf = append(buf, bytes.Repeat(e.indentStr, code.indent+1)...)
buf = append(buf, []byte(kv.key)...)
buf = append(buf, []byte{':', ' '}...)
buf = append(buf, []byte(kv.value)...)
}
buf = append(buf, '\n')
buf = append(buf, e.prefix...)
buf = append(buf, bytes.Repeat(e.indentStr, code.indent)...)
buf = append(buf, '}')
e.buf = e.buf[:pos[0]]
e.buf = append(e.buf, buf...)
code = code.next
case opStructFieldRecursive:
ptr := load(ctxptr, code.idx)
if ptr != 0 {

20
json.go
View File

@ -154,8 +154,18 @@ type Unmarshaler interface {
// an infinite recursion.
//
func Marshal(v interface{}) ([]byte, error) {
return MarshalWithOption(v)
}
// MarshalWithOption returns the JSON encoding of v with EncodeOption.
func MarshalWithOption(v interface{}, opts ...EncodeOption) ([]byte, error) {
var b *bytes.Buffer
enc := NewEncoder(b)
for _, opt := range opts {
if err := opt(enc); err != nil {
return nil, err
}
}
bytes, err := enc.encodeForMarshal(v)
if err != nil {
enc.release()
@ -169,8 +179,18 @@ func Marshal(v interface{}) ([]byte, error) {
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
return MarshalIndentWithOption(v, prefix, indent)
}
// MarshalIndentWithOption is like Marshal but applies Indent to format the output with EncodeOption.
func MarshalIndentWithOption(v interface{}, prefix, indent string, opts ...EncodeOption) ([]byte, error) {
var b *bytes.Buffer
enc := NewEncoder(b)
for _, opt := range opts {
if err := opt(enc); err != nil {
return nil, err
}
}
enc.SetIndent(prefix, indent)
bytes, err := enc.encodeForMarshal(v)
if err != nil {