add omitnil for map and slice

This commit is contained in:
Ya Bin Ma 2023-02-28 07:15:56 +08:00
parent a2149a5b25
commit cbb3c17c5e
6 changed files with 136 additions and 2 deletions

View File

@ -637,6 +637,8 @@ func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType {
headType := code.ToHeaderType(tag.IsString)
if tag.IsOmitEmpty {
headType = headType.HeadToOmitEmptyHead()
} else if tag.IsOmitNil {
headType = headType.HeadToOmitNilHead()
}
return headType
}
@ -645,6 +647,8 @@ func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType {
fieldType := code.ToFieldType(tag.IsString)
if tag.IsOmitEmpty {
fieldType = fieldType.FieldToOmitEmptyField()
} else if tag.IsOmitNil {
fieldType = fieldType.FieldToOmitNilField()
}
return fieldType
}

View File

@ -37,6 +37,12 @@ func (t OpType) IsMultipleOpHead() bool {
return true
case OpStructHeadOmitEmptyMap:
return true
case OpStructHeadOmitNilSlice:
return true
case OpStructHeadOmitNilArray:
return true
case OpStructHeadOmitNilMap:
return true
case OpStructHeadOmitEmptyStruct:
return true
case OpStructHeadSlicePtr:
@ -75,6 +81,12 @@ func (t OpType) IsMultipleOpField() bool {
return true
case OpStructFieldOmitEmptyMap:
return true
case OpStructFieldOmitNilSlice:
return true
case OpStructFieldOmitNilArray:
return true
case OpStructFieldOmitNilMap:
return true
case OpStructFieldOmitEmptyStruct:
return true
case OpStructFieldSlicePtr:

View File

@ -22,7 +22,7 @@ const (
CodeStructEnd CodeType = 11
)
var opTypeStrings = [400]string{
var opTypeStrings = [411]string{
"End",
"Interface",
"Ptr",
@ -423,6 +423,17 @@ var opTypeStrings = [400]string{
"StructFieldOmitEmpty",
"StructEnd",
"StructEndOmitEmpty",
"400",
"StructHeadOmitNilArray",
"StructFieldOmitNilArray",
"403",
"404",
"StructHeadOmitNilMap",
"StructFieldOmitNilMap",
"407",
"408",
"StructHeadOmitNilSlice",
"StructFieldOmitNilSlice",
}
type OpType uint16
@ -828,10 +839,17 @@ const (
OpStructFieldOmitEmpty OpType = 397
OpStructEnd OpType = 398
OpStructEndOmitEmpty OpType = 399
OpStructHeadOmitNilArray OpType = 401
OpStructHeadOmitNilMap OpType = 405
OpStructHeadOmitNilSlice OpType = 409
OpStructFieldOmitNilArray OpType = 402
OpStructFieldOmitNilMap OpType = 406
OpStructFieldOmitNilSlice OpType = 410
)
func (t OpType) String() string {
if int(t) >= 400 {
if int(t) >= 411 {
return ""
}
return opTypeStrings[int(t)]
@ -893,6 +911,15 @@ func (t OpType) HeadToOmitEmptyHead() OpType {
return t
}
func (t OpType) HeadToOmitNilHead() OpType {
const toOmitNilOffset = 313
if strings.Contains(OpType(int(t)+toOmitNilOffset).String(), "OmitNil") {
return OpType(int(t) + toOmitNilOffset)
}
return t
}
func (t OpType) PtrHeadToHead() OpType {
idx := strings.Index(t.String(), "PtrHead")
if idx == -1 {
@ -930,3 +957,11 @@ func (t OpType) FieldToOmitEmptyField() OpType {
}
return t
}
func (t OpType) FieldToOmitNilField() OpType {
const toOmitNilOffset = 142
if strings.Contains(OpType(int(t)+toOmitNilOffset).String(), "OmitNil") {
return OpType(int(t) + toOmitNilOffset)
}
return t
}

View File

@ -2725,6 +2725,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructHeadOmitNilSlice:
p := load(ctxptr, code.Idx)
if p == 0 {
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendNullComma(ctx, b)
}
code = code.End.Next
break
}
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
slice := ptrToSlice(p)
if slice.Data == nil {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructPtrHeadArrayPtr, encoder.OpStructPtrHeadSlicePtr:
p := load(ctxptr, code.Idx)
if p == 0 {
@ -2854,6 +2875,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructHeadOmitNilMap:
p := load(ctxptr, code.Idx)
if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 {
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendNullComma(ctx, b)
}
code = code.End.Next
break
}
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 {
p = ptrToPtr(p + uintptr(code.Offset))
}
if ptrToUnsafePtr(p) == nil {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructPtrHeadMapPtr:
p := load(ctxptr, code.Idx)
if p == 0 {
@ -4082,6 +4125,17 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructFieldOmitNilSlice:
p := load(ctxptr, code.Idx)
p += uintptr(code.Offset)
slice := ptrToSlice(p)
if slice.Data == nil {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructFieldSlicePtr:
b = appendStructKey(ctx, code, b)
p := load(ctxptr, code.Idx)
@ -4114,6 +4168,16 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructFieldOmitNilMap:
p := load(ctxptr, code.Idx)
p = ptrToPtr(p + uintptr(code.Offset))
if ptrToUnsafePtr(p) == nil {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
code = code.Next
store(ctxptr, code.Idx, p)
}
case encoder.OpStructFieldMapPtr:
b = appendStructKey(ctx, code, b)
p := load(ctxptr, code.Idx)

View File

@ -33,6 +33,7 @@ type StructTag struct {
Key string
IsTaggedKey bool
IsOmitEmpty bool
IsOmitNil bool
IsString bool
Field reflect.StructField
}
@ -82,6 +83,8 @@ func StructTagFromField(field reflect.StructField) *StructTag {
switch opt {
case "omitempty":
st.IsOmitEmpty = true
case "omitnil":
st.IsOmitNil = true
case "string":
st.IsString = true
}

View File

@ -11,6 +11,7 @@ import (
"log"
"os"
"strings"
"testing"
"github.com/goccy/go-json"
)
@ -311,3 +312,18 @@ func ExampleHTMLEscape() {
// Output:
//{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"}
}
func TestOmitNil(t *testing.T) {
type Foo struct {
Bar []string `json:"bar,omitnil"`
Baz map[string]string `json:"baz,omitnil"`
}
var a, b Foo
b.Bar = []string{} // <- empty, not nil
b.Baz = map[string]string{} // <- empty, not nil
e := json.NewEncoder(os.Stdout)
_ = e.Encode(a)
println("")
_ = e.Encode(b)
}