Skip to content

Commit

Permalink
feat: support tag omitzero
Browse files Browse the repository at this point in the history
  • Loading branch information
AsterDY committed Feb 26, 2025
1 parent c09ac55 commit bbb8379
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 9 deletions.
17 changes: 13 additions & 4 deletions internal/encoder/alg/primitives.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Copyright 2024 ByteDance Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -19,8 +19,11 @@ package alg
import (
"encoding"
"encoding/json"
"reflect"
"unsafe"

"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/resolver"
"github.com/bytedance/sonic/internal/rt"
)

Expand Down Expand Up @@ -92,4 +95,10 @@ func EncodeTextMarshaler(buf *[]byte, val encoding.TextMarshaler, opt uint64) er
return nil
}
}


func IsZero(val unsafe.Pointer, fv *resolver.FieldMeta) bool {
rv := reflect.NewAt(fv.Type, val).Elem()
b1 := fv.IsZero == nil && rv.IsZero()
b2 := fv.IsZero != nil && fv.IsZero(rv)
return b1 || b2
}
12 changes: 9 additions & 3 deletions internal/encoder/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ func (self *Compiler) compileStructBody(p *ir.Program, sp int, vt reflect.Type)
p.Add(ir.OP_cond_set)

/* compile each field */
for _, fv := range resolver.ResolveStruct(vt) {
fvs := resolver.ResolveStruct(vt)
for i, fv := range fvs {
var s []int
var o resolver.Offset

Expand All @@ -463,7 +464,12 @@ func (self *Compiler) compileStructBody(p *ir.Program, sp int, vt reflect.Type)
/* check for "omitempty" option */
if fv.Type.Kind() != reflect.Struct && fv.Type.Kind() != reflect.Array && (fv.Opts&resolver.F_omitempty) != 0 {
s = append(s, p.PC())
self.compileStructFieldZero(p, fv.Type)
self.compileStructFieldEmpty(p, fv.Type)
}
/* check for "omptyzero" option */
if fv.Opts&resolver.F_omitzero != 0 {
s = append(s, p.PC())
p.VField(ir.OP_is_zero, &fvs[i])
}

/* add the comma if not the first element */
Expand Down Expand Up @@ -574,7 +580,7 @@ func (self *Compiler) compileStructFieldStr(p *ir.Program, sp int, vt reflect.Ty
}
}

func (self *Compiler) compileStructFieldZero(p *ir.Program, vt reflect.Type) {
func (self *Compiler) compileStructFieldEmpty(p *ir.Program, vt reflect.Type) {
switch vt.Kind() {
case reflect.Bool:
p.Add(ir.OP_is_zero_1)
Expand Down
191 changes: 191 additions & 0 deletions internal/encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"runtime"
"runtime/debug"
"strconv"
Expand Down Expand Up @@ -639,4 +640,194 @@ func BenchmarkEncode_Float32(b *testing.B) {
b.Run(name, lib.test)
}
}
}

type NonZeroStruct struct{}

func (nzs NonZeroStruct) IsZero() bool {
return false
}

type NoPanicStruct struct {
Int int `json:"int,omitzero"`
}

func (nps *NoPanicStruct) IsZero() bool {
return nps.Int != 0
}

type isZeroer interface {
IsZero() bool
}

type OptionalsZero struct {
Sr string `json:"sr"`
So string `json:"so,omitzero"`
Sw string `json:"-"`

Ir int `json:"omitzero"` // actually named omitzero, not an option
Io int `json:"io,omitzero"`

Slr []string `json:"slr,random"`
Slo []string `json:"slo,omitzero"`
SloNonNil []string `json:"slononnil,omitzero"`

Mr map[string]interface{} `json:"mr"`
Mo map[string]interface{} `json:",omitzero"`
Moo map[string]interface{} `json:"moo,omitzero"`

Fr float64 `json:"fr"`
Fo float64 `json:"fo,omitzero"`
Foo float64 `json:"foo,omitzero"`
Foo2 [2]float64 `json:"foo2,omitzero"`

Br bool `json:"br"`
Bo bool `json:"bo,omitzero"`

Ur uint `json:"ur"`
Uo uint `json:"uo,omitzero"`

Str struct{} `json:"str"`
Sto struct{} `json:"sto,omitzero"`

Time time.Time `json:"time,omitzero"`
TimeLocal time.Time `json:"timelocal,omitzero"`
Nzs NonZeroStruct `json:"nzs,omitzero"`

NilIsZeroer isZeroer `json:"niliszeroer,omitzero"` // nil interface
NonNilIsZeroer isZeroer `json:"nonniliszeroer,omitzero"` // non-nil interface
NoPanicStruct0 isZeroer `json:"nps0,omitzero"` // non-nil interface with nil pointer
NoPanicStruct1 isZeroer `json:"nps1,omitzero"` // non-nil interface with non-nil pointer
NoPanicStruct2 *NoPanicStruct `json:"nps2,omitzero"` // nil pointer
NoPanicStruct3 *NoPanicStruct `json:"nps3,omitzero"` // non-nil pointer
NoPanicStruct4 NoPanicStruct `json:"nps4,omitzero"` // concrete type
}

func TestOmitZero(t *testing.T) {
// ForceUseVM()
const want = `{
"sr": "",
"omitzero": 0,
"slr": null,
"slononnil": [],
"mr": {},
"Mo": {},
"fr": 0,
"br": false,
"ur": 0,
"str": {},
"nzs": {},
"nps1": {},
"nps3": {},
"nps4": {}
}`
var o OptionalsZero
o.Sw = "something"
o.SloNonNil = make([]string, 0)
o.Mr = map[string]interface{}{}
o.Mo = map[string]interface{}{}

o.Foo = -0
o.Foo2 = [2]float64{+0, -0}

o.TimeLocal = time.Time{}.Local()

o.NonNilIsZeroer = time.Time{}
o.NoPanicStruct0 = (*NoPanicStruct)(nil)
o.NoPanicStruct1 = &NoPanicStruct{}
o.NoPanicStruct3 = &NoPanicStruct{}

got, err := EncodeIndented(&o, "", " ", 0)
if err != nil {
t.Fatalf("MarshalIndent error: %v", err)
}
if got := string(got); got != want {
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
}
}

func TestOmitZeroMap(t *testing.T) {
const want = `{
"foo": {
"sr": "",
"omitzero": 0,
"slr": null,
"mr": null,
"fr": 0,
"br": false,
"ur": 0,
"str": {},
"nzs": {},
"nps4": {}
}
}`
m := map[string]OptionalsZero{"foo": {}}
got, err := EncodeIndented(m, "", " ", 0)
if err != nil {
t.Fatalf("MarshalIndent error: %v", err)
}
if got := string(got); got != want {
fmt.Println(got)
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
}
}

type OptionalsEmptyZero struct {
Sr string `json:"sr"`
So string `json:"so,omitempty,omitzero"`
Sw string `json:"-"`

Io int `json:"io,omitempty,omitzero"`

Slr []string `json:"slr,random"`
Slo []string `json:"slo,omitempty,omitzero"`
SloNonNil []string `json:"slononnil,omitempty,omitzero"`

Mr map[string]interface{} `json:"mr"`
Mo map[string]interface{} `json:",omitempty,omitzero"`

Fr float64 `json:"fr"`
Fo float64 `json:"fo,omitempty,omitzero"`

Br bool `json:"br"`
Bo bool `json:"bo,omitempty,omitzero"`

Ur uint `json:"ur"`
Uo uint `json:"uo,omitempty,omitzero"`

Str struct{} `json:"str"`
Sto struct{} `json:"sto,omitempty,omitzero"`

Time time.Time `json:"time,omitempty,omitzero"`
Nzs NonZeroStruct `json:"nzs,omitempty,omitzero"`
}

func TestOmitEmptyZero(t *testing.T) {
const want = `{
"sr": "",
"slr": null,
"mr": {},
"fr": 0,
"br": false,
"ur": 0,
"str": {},
"nzs": {}
}`
var o OptionalsEmptyZero
o.Sw = "something"
o.SloNonNil = make([]string, 0)
o.Mr = map[string]interface{}{}
o.Mo = map[string]interface{}{}

got, err := EncodeIndented(&o, "", " ", 0)
if err != nil {
t.Fatalf("MarshalIndent error: %v", err)
}
if got := string(got); got != want {
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
}
}

func indentNewlines(s string) string {
return strings.Join(strings.Split(s, "\n"), "\n\t")
}
22 changes: 22 additions & 0 deletions internal/encoder/ir/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"unsafe"

"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/resolver"
"github.com/bytedance/sonic/internal/rt"
)

Expand Down Expand Up @@ -80,6 +81,7 @@ const (
OP_marshal_text_p
OP_cond_set
OP_cond_testc
OP_is_zero
)

const (
Expand Down Expand Up @@ -229,6 +231,11 @@ type typAndTab struct {
itab *rt.GoItab
}

type typAndField struct {
vt reflect.Type
fv *resolver.FieldMeta
}

func NewInsVtab(op Op, vt reflect.Type, itab *rt.GoItab) Instr {
return Instr{
o: op,
Expand All @@ -239,6 +246,13 @@ func NewInsVtab(op Op, vt reflect.Type, itab *rt.GoItab) Instr {
}
}

func NewInsField(op Op, fv *resolver.FieldMeta) Instr {
return Instr{
o: op,
p: unsafe.Pointer(fv),
}
}

func NewInsVp(op Op, vt reflect.Type, pv bool) Instr {
i := 0
if pv {
Expand All @@ -263,6 +277,10 @@ func (self Instr) Vf() uint8 {
return (*rt.GoType)(self.p).KindFlags
}

func (self Instr) VField() (*resolver.FieldMeta) {
return (*resolver.FieldMeta)(self.p)
}

func (self Instr) Vs() (v string) {
(*rt.GoString)(unsafe.Pointer(&v)).Ptr = self.p
(*rt.GoString)(unsafe.Pointer(&v)).Len = self.Vi()
Expand Down Expand Up @@ -442,6 +460,10 @@ func (self *Program) Vtab(op Op, vt reflect.Type, itab *rt.GoItab) {
*self = append(*self, NewInsVtab(op, vt, itab))
}

func (self *Program) VField(op Op, fv *resolver.FieldMeta) {
*self = append(*self, NewInsField(op, fv))
}

func (self Program) Disassemble() string {
nb := len(self)
tab := make([]bool, nb+1)
Expand Down
6 changes: 6 additions & 0 deletions internal/encoder/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,12 @@ func Execute(b *[]byte, p unsafe.Pointer, s *vars.Stack, flags uint64, prog *ir.
pc = ins.Vi()
continue
}
case ir.OP_is_zero:
fv := ins.VField()
if alg.IsZero(p, fv) {
pc = ins.Vi()
continue
}
case ir.OP_is_zero_1:
if *(*uint8)(p) == 0 {
pc = ins.Vi()
Expand Down
15 changes: 15 additions & 0 deletions internal/encoder/x86/assembler_regabi_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ var _OpFuncTab = [256]func(*Assembler, *ir.Instr){
ir.OP_marshal_text_p: (*Assembler)._asm_OP_marshal_text_p,
ir.OP_cond_set: (*Assembler)._asm_OP_cond_set,
ir.OP_cond_testc: (*Assembler)._asm_OP_cond_testc,
ir.OP_is_zero: (*Assembler)._asm_OP_is_zero,
}

func (self *Assembler) instr(v *ir.Instr) {
Expand Down Expand Up @@ -1097,6 +1098,20 @@ func (self *Assembler) _asm_OP_is_zero_map(p *ir.Instr) {
self.Xjmp("JE", p.Vi()) // JE p.Vi()
}

var (
_F_is_zero = jit.Func(alg.IsZero)
_T_reflect_Type = rt.UnpackIface(reflect.Type(nil))
)

func (self *Assembler) _asm_OP_is_zero(p *ir.Instr) {
fv := p.VField()
self.Emit("MOVQ", _SP_p, _AX) // ptr
self.Emit("MOVQ", jit.ImmPtr(unsafe.Pointer(fv)), _BX) // fv
self.call_go(_F_is_zero) // CALL $fn
self.Emit("CMPB", _AX, jit.Imm(0)) // CMPB (SP.p), $0
self.Xjmp("JNE", p.Vi()) // JE p.Vi()
}

func (self *Assembler) _asm_OP_goto(p *ir.Instr) {
self.Xjmp("JMP", p.Vi())
}
Expand Down
Loading

0 comments on commit bbb8379

Please sign in to comment.