mirror of
https://github.com/netbirdio/easyjson.git
synced 2026-05-22 18:44:42 -07:00
Support for json:",omitzero" tag (#429)
* add support for the json:",omitzero" tag
omitzero was added to the go stdlib encoder/json in go 1.24
* add case which omitempty missed: uintptr is encodable in json
and does accept omitempty in the go stdlib encoder/json.
* add omitzero test cases for pointer-to-"" and pointer-to-{}
(both should marshal to something in order to match stdlib json, and
they do)
* fix and add tests for the case when omitzero is combined with omitempty
Without a special case the generated code can look like
if v != nil && v != nil {
or
if v != "" && v != "" {
and that gets flagged by go vet (go 1.25.5)
This commit is contained in:
@@ -17,6 +17,7 @@ generate: build
|
||||
./tests/snake.go \
|
||||
./tests/data.go \
|
||||
./tests/omitempty.go \
|
||||
./tests/omitzero.go \
|
||||
./tests/nothing.go \
|
||||
./tests/named_type.go \
|
||||
./tests/custom_map_key_type.go \
|
||||
@@ -54,6 +55,7 @@ generate: build
|
||||
./tests/text_marshaler.go
|
||||
bin/easyjson -snake_case ./tests/snake.go
|
||||
bin/easyjson -omit_empty ./tests/omitempty.go
|
||||
bin/easyjson -omit_zero ./tests/omitzero.go
|
||||
bin/easyjson -build_tags=use_easyjson -disable_members_unescape ./benchmark/data.go
|
||||
bin/easyjson -disallow_unknown_fields ./tests/disallow_unknown.go
|
||||
bin/easyjson -disable_members_unescape ./tests/members_unescaped.go
|
||||
|
||||
@@ -30,6 +30,7 @@ type Generator struct {
|
||||
SnakeCase bool
|
||||
LowerCamelCase bool
|
||||
OmitEmpty bool
|
||||
OmitZero bool
|
||||
DisallowUnknownFields bool
|
||||
SkipMemberNameUnescaping bool
|
||||
|
||||
@@ -125,6 +126,9 @@ func (g *Generator) writeMain() (path string, err error) {
|
||||
if g.OmitEmpty {
|
||||
fmt.Fprintln(f, " g.OmitEmpty()")
|
||||
}
|
||||
if g.OmitZero {
|
||||
fmt.Fprintln(f, " g.OmitZero()")
|
||||
}
|
||||
if g.NoStdMarshalers {
|
||||
fmt.Fprintln(f, " g.NoStdMarshalers()")
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ var snakeCase = flag.Bool("snake_case", false, "use snake_case names instead of
|
||||
var lowerCamelCase = flag.Bool("lower_camel_case", false, "use lowerCamelCase names instead of CamelCase by default")
|
||||
var noStdMarshalers = flag.Bool("no_std_marshalers", false, "don't generate MarshalJSON/UnmarshalJSON funcs")
|
||||
var omitEmpty = flag.Bool("omit_empty", false, "omit empty fields by default")
|
||||
var omitZero = flag.Bool("omit_zero", false, "omit zero value fields by default")
|
||||
var allStructs = flag.Bool("all", false, "generate marshaler/unmarshalers for all structs in a file")
|
||||
var simpleBytes = flag.Bool("byte", false, "use simple bytes instead of Base64Bytes for slice of bytes")
|
||||
var leaveTemps = flag.Bool("leave_temps", false, "do not delete temporary files")
|
||||
@@ -86,6 +87,7 @@ func generate(fname string) (err error) {
|
||||
DisallowUnknownFields: *disallowUnknownFields,
|
||||
SkipMemberNameUnescaping: *skipMemberNameUnescaping,
|
||||
OmitEmpty: *omitEmpty,
|
||||
OmitZero: *omitZero,
|
||||
LeaveTemps: *leaveTemps,
|
||||
OutName: outName,
|
||||
StubsOnly: *stubs,
|
||||
|
||||
+90
-4
@@ -56,6 +56,8 @@ type fieldTags struct {
|
||||
omit bool
|
||||
omitEmpty bool
|
||||
noOmitEmpty bool
|
||||
omitZero bool
|
||||
noOmitZero bool
|
||||
asString bool
|
||||
required bool
|
||||
intern bool
|
||||
@@ -76,6 +78,10 @@ func parseFieldTags(f reflect.StructField) fieldTags {
|
||||
ret.omitEmpty = true
|
||||
case s == "!omitempty":
|
||||
ret.noOmitEmpty = true
|
||||
case s == "omitzero":
|
||||
ret.omitZero = true
|
||||
case s == "!omitzero":
|
||||
ret.noOmitZero = true
|
||||
case s == "string":
|
||||
ret.asString = true
|
||||
case s == "required":
|
||||
@@ -314,7 +320,8 @@ func (g *Generator) notEmptyCheck(t reflect.Type, v string) string {
|
||||
return v + ` != ""`
|
||||
case reflect.Float32, reflect.Float64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
|
||||
return v + " != 0"
|
||||
|
||||
@@ -324,6 +331,78 @@ func (g *Generator) notEmptyCheck(t reflect.Type, v string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Generator) notZeroCheck(t reflect.Type, v string) string {
|
||||
optionalIface := reflect.TypeOf((*easyjson.IsZero)(nil)).Elem()
|
||||
if reflect.PtrTo(t).Implements(optionalIface) {
|
||||
return "!(" + v + ").IsZero()"
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Interface, reflect.Ptr:
|
||||
return v + " != nil"
|
||||
case reflect.Bool:
|
||||
return v
|
||||
case reflect.String:
|
||||
return v + ` != ""`
|
||||
case reflect.Float32, reflect.Float64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
|
||||
return v + " != 0"
|
||||
case reflect.Array:
|
||||
// NOTE: stdlib encoding/json does not check if array elements implement IsZero, so we don't either
|
||||
return "(" + v + " != " + g.getType(t) + "{})"
|
||||
case reflect.Struct:
|
||||
// NOTE: stdlib encoding/json does not check if struct fields implement IsZero, so we don't either
|
||||
return "(" + v + " != " + g.getType(t) + "{})"
|
||||
|
||||
default:
|
||||
return "true"
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Generator) notEmptyOrZeroCheck(t reflect.Type, v string) string {
|
||||
isDefinedIface := reflect.TypeOf((*easyjson.Optional)(nil)).Elem()
|
||||
implementsIsDefined := reflect.PtrTo(t).Implements(isDefinedIface)
|
||||
isZeroIface := reflect.TypeOf((*easyjson.IsZero)(nil)).Elem()
|
||||
implementsIsZero := reflect.PtrTo(t).Implements(isZeroIface)
|
||||
|
||||
if implementsIsDefined && implementsIsZero {
|
||||
return "(" + v + ").IsDefined() || !(" + v + ").IsZero()"
|
||||
} else if implementsIsDefined {
|
||||
return "(" + v + ").IsDefined()"
|
||||
} else if implementsIsZero {
|
||||
return "!(" + v + ").IsZero()"
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return v + " != nil && len(" + v + ") != 0"
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v + " != nil"
|
||||
case reflect.Bool:
|
||||
return v
|
||||
case reflect.String:
|
||||
return v + ` != ""`
|
||||
case reflect.Float32, reflect.Float64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
|
||||
return v + " != 0"
|
||||
case reflect.Array:
|
||||
// NOTE: stdlib encoding/json does not check if array elements implement IsZero, so we don't either
|
||||
return "(" + v + " != " + g.getType(t) + "{})"
|
||||
case reflect.Struct:
|
||||
// NOTE: stdlib encoding/json does not check if struct fields implement IsZero, so we don't either
|
||||
return "(" + v + " != " + g.getType(t) + "{})"
|
||||
|
||||
default:
|
||||
return "true"
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField, first, firstCondition bool) (bool, error) {
|
||||
jsonName := g.fieldNamer.GetJSONFieldName(t, f)
|
||||
tags := parseFieldTags(f)
|
||||
@@ -335,18 +414,25 @@ func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField,
|
||||
toggleFirstCondition := firstCondition
|
||||
|
||||
noOmitEmpty := (!tags.omitEmpty && !g.omitEmpty) || tags.noOmitEmpty
|
||||
if noOmitEmpty {
|
||||
noOmitZero := (!tags.omitZero && !g.omitZero) || tags.noOmitZero
|
||||
if noOmitEmpty && noOmitZero {
|
||||
fmt.Fprintln(g.out, " {")
|
||||
toggleFirstCondition = false
|
||||
} else {
|
||||
} else if noOmitZero {
|
||||
fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{")
|
||||
// can be any in runtime, so toggleFirstCondition stay as is
|
||||
} else if noOmitEmpty {
|
||||
fmt.Fprintln(g.out, " if", g.notZeroCheck(f.Type, "in."+f.Name), "{")
|
||||
// can be any in runtime, so toggleFirstCondition stay as is
|
||||
} else {
|
||||
fmt.Fprintln(g.out, " if", g.notEmptyOrZeroCheck(f.Type, "in."+f.Name), "{")
|
||||
// can be any in runtime, so toggleFirstCondition stay as is
|
||||
}
|
||||
|
||||
if firstCondition {
|
||||
fmt.Fprintf(g.out, " const prefix string = %q\n", ","+strconv.Quote(jsonName)+":")
|
||||
if first {
|
||||
if !noOmitEmpty {
|
||||
if !noOmitEmpty || !noOmitZero {
|
||||
fmt.Fprintln(g.out, " first = false")
|
||||
}
|
||||
fmt.Fprintln(g.out, " out.RawString(prefix[1:])")
|
||||
|
||||
@@ -35,6 +35,7 @@ type Generator struct {
|
||||
|
||||
noStdMarshalers bool
|
||||
omitEmpty bool
|
||||
omitZero bool
|
||||
disallowUnknownFields bool
|
||||
fieldNamer FieldNamer
|
||||
simpleBytes bool
|
||||
@@ -128,6 +129,11 @@ func (g *Generator) OmitEmpty() {
|
||||
g.omitEmpty = true
|
||||
}
|
||||
|
||||
// OmitZero triggers `json=",omitzero"` behaviour by default.
|
||||
func (g *Generator) OmitZero() {
|
||||
g.omitZero = true
|
||||
}
|
||||
|
||||
// SimpleBytes triggers generate output bytes as slice byte
|
||||
func (g *Generator) SimpleBytes() {
|
||||
g.simpleBytes = true
|
||||
|
||||
@@ -33,6 +33,11 @@ type Optional interface {
|
||||
IsDefined() bool
|
||||
}
|
||||
|
||||
// IsZero defines a zero-test method for a type to integrate with 'omitzero' logic.
|
||||
type IsZero interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
// UnknownsUnmarshaler provides a method to unmarshal unknown struct fileds and save them as you want
|
||||
type UnknownsUnmarshaler interface {
|
||||
UnmarshalUnknown(in *jlexer.Lexer, key string)
|
||||
|
||||
@@ -24,8 +24,11 @@ var testCases = []struct {
|
||||
{&namedPrimitiveTypesValue, namedPrimitiveTypesString},
|
||||
{&structsValue, structsString},
|
||||
{&omitEmptyValue, omitEmptyString},
|
||||
{&omitZeroValue, omitZeroString},
|
||||
{&omitEmptyAndZeroValue, omitEmptyAndZeroString},
|
||||
{&snakeStructValue, snakeStructString},
|
||||
{&omitEmptyDefaultValue, omitEmptyDefaultString},
|
||||
{&omitZeroDefaultValue, omitZeroDefaultString},
|
||||
{&optsValue, optsString},
|
||||
{&rawValue, rawString},
|
||||
{&stdMarshalerValue, stdMarshalerString},
|
||||
|
||||
@@ -358,6 +358,80 @@ var omitEmptyString = "{" +
|
||||
`"SubPNE":{"Value":"3","Value2":"4"}` +
|
||||
"}"
|
||||
|
||||
type OmitZero struct {
|
||||
// NOTE: first field is empty to test comma printing.
|
||||
|
||||
StrZ, StrNZ string `json:",omitzero"`
|
||||
ZPtr, PtrZ, PtrNZ *string `json:",omitzero"`
|
||||
|
||||
IntNZ int `json:"intField,omitzero"`
|
||||
IntZ int `json:",omitzero"`
|
||||
|
||||
// NOTE: omitzero DOES have effect on non-pointer struct fields.
|
||||
SubZ, SubNZ SubStruct `json:",omitzero"`
|
||||
SubZP, SubPZ, SubPNZ *SubStruct `json:",omitzero"`
|
||||
|
||||
// test IsZero()bool is respected
|
||||
Time time.Time `json:",omitzero"`
|
||||
}
|
||||
|
||||
var omitZeroValue = OmitZero{
|
||||
StrNZ: "str",
|
||||
PtrZ: new(string),
|
||||
PtrNZ: &str,
|
||||
IntNZ: 6,
|
||||
SubNZ: SubStruct{Value: "1", Value2: "2"},
|
||||
SubPZ: &SubStruct{},
|
||||
SubPNZ: &SubStruct{Value: "3", Value2: "4"},
|
||||
}
|
||||
|
||||
var omitZeroString = "{" +
|
||||
`"StrNZ":"str",` +
|
||||
`"PtrZ":"",` +
|
||||
`"PtrNZ":"bla",` +
|
||||
`"intField":6,` +
|
||||
`"SubNZ":{"Value":"1","Value2":"2"},` +
|
||||
`"SubPZ":{"Value":"","Value2":""},` +
|
||||
`"SubPNZ":{"Value":"3","Value2":"4"}` +
|
||||
"}"
|
||||
|
||||
type OmitEmptyAndZero struct {
|
||||
// NOTE: first field is empty to test comma printing.
|
||||
|
||||
StrZ, StrNZ string `json:",omitempty,omitzero"`
|
||||
ZPtr, PtrZ, PtrNZ *string `json:",omitempty,omitzero"`
|
||||
|
||||
IntNZ int `json:"intField,omitempty,omitzero"`
|
||||
IntZ int `json:",omitempty,omitzero"`
|
||||
|
||||
// NOTE: omitzero DOES have effect on non-pointer struct fields.
|
||||
SubZ, SubNZ SubStruct `json:",omitempty,omitzero"`
|
||||
SubZP, SubPZ, SubPNZ *SubStruct `json:",omitempty,omitzero"`
|
||||
|
||||
// test IsZero()bool is respected
|
||||
Time time.Time `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
var omitEmptyAndZeroValue = OmitZero{
|
||||
StrNZ: "str",
|
||||
PtrZ: new(string),
|
||||
PtrNZ: &str,
|
||||
IntNZ: 6,
|
||||
SubNZ: SubStruct{Value: "1", Value2: "2"},
|
||||
SubPZ: &SubStruct{},
|
||||
SubPNZ: &SubStruct{Value: "3", Value2: "4"},
|
||||
}
|
||||
|
||||
var omitEmptyAndZeroString = "{" +
|
||||
`"StrNZ":"str",` +
|
||||
`"PtrZ":"",` +
|
||||
`"PtrNZ":"bla",` +
|
||||
`"intField":6,` +
|
||||
`"SubNZ":{"Value":"1","Value2":"2"},` +
|
||||
`"SubPZ":{"Value":"","Value2":""},` +
|
||||
`"SubPNZ":{"Value":"3","Value2":"4"}` +
|
||||
"}"
|
||||
|
||||
type Opts struct {
|
||||
StrNull opt.String
|
||||
StrEmpty opt.String
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package tests
|
||||
|
||||
import "time"
|
||||
|
||||
//easyjson:json
|
||||
type OmitZeroDefault struct {
|
||||
Field string
|
||||
Str string
|
||||
Str1 string `json:"s,!omitzero"`
|
||||
Str2 string `json:",!omitzero"`
|
||||
Time time.Time `json:",omitzero"` // implements IsZero() bool
|
||||
Array1 [2]int `json:",omitzero"` // array filled with zero values is omitted
|
||||
Array2 [2]int `json:",omitzero"` // array filled with non-zero values is outputed
|
||||
Array3 [2]OmitZeroSubstruct `json:",omitzero"`
|
||||
Array4 [2]OmitZeroSubstruct `json:",omitzero"`
|
||||
Struct1 OmitZeroSubstruct `json:",omitzero"`
|
||||
Struct2 OmitZeroSubstruct `json:",!omitzero"` // struct where all the fields are tagged omitzero
|
||||
}
|
||||
|
||||
var omitZeroDefaultValue = OmitZeroDefault{Field: "test", Array2: [2]int{0, 1}, Array4: [2]OmitZeroSubstruct{{}, {F: 1}}}
|
||||
var omitZeroDefaultString = `{"Field":"test","s":"","Str2":"","Array2":[0,1],"Array4":[{},{"F":1}],"Struct2":{}}`
|
||||
|
||||
type OmitZeroSubstruct struct {
|
||||
F float32 `json:",omitzero"`
|
||||
T time.Time `json:",omitzero"`
|
||||
}
|
||||
Reference in New Issue
Block a user