Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Generator struct {
SnakeCase bool
LowerCamelCase bool
OmitEmpty bool
OmitZero bool
DisallowUnknownFields bool
SkipMemberNameUnescaping bool

Expand Down Expand Up @@ -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()")
}
Expand Down
2 changes: 2 additions & 0 deletions easyjson/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -86,6 +87,7 @@ func generate(fname string) (err error) {
DisallowUnknownFields: *disallowUnknownFields,
SkipMemberNameUnescaping: *skipMemberNameUnescaping,
OmitEmpty: *omitEmpty,
OmitZero: *omitZero,
LeaveTemps: *leaveTemps,
OutName: outName,
StubsOnly: *stubs,
Expand Down
53 changes: 49 additions & 4 deletions gen/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type fieldTags struct {
omit bool
omitEmpty bool
noOmitEmpty bool
omitZero bool
noOmitZero bool
asString bool
required bool
intern bool
Expand All @@ -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":
Expand Down Expand Up @@ -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"

Expand All @@ -324,6 +331,37 @@ 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) genStructFieldEncoder(t reflect.Type, f reflect.StructField, first, firstCondition bool) (bool, error) {
jsonName := g.fieldNamer.GetJSONFieldName(t, f)
tags := parseFieldTags(f)
Expand All @@ -335,18 +373,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.notEmptyCheck(f.Type, "in."+f.Name), "&&", g.notZeroCheck(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:])")
Expand Down
6 changes: 6 additions & 0 deletions gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Generator struct {

noStdMarshalers bool
omitEmpty bool
omitZero bool
disallowUnknownFields bool
fieldNamer FieldNamer
simpleBytes bool
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions tests/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ var testCases = []struct {
{&namedPrimitiveTypesValue, namedPrimitiveTypesString},
{&structsValue, structsString},
{&omitEmptyValue, omitEmptyString},
{&omitZeroValue, omitZeroString},
{&snakeStructValue, snakeStructString},
{&omitEmptyDefaultValue, omitEmptyDefaultString},
{&omitZeroDefaultValue, omitZeroDefaultString},
{&optsValue, optsString},
{&rawValue, rawString},
{&stdMarshalerValue, stdMarshalerString},
Expand Down
33 changes: 33 additions & 0 deletions tests/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,39 @@ var omitEmptyString = "{" +
`"SubPNE":{"Value":"3","Value2":"4"}` +
"}"

type OmitZero struct {
// NOTE: first field is empty to test comma printing.

StrZ, StrNZ string `json:",omitzero"`
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"`
SubPZ, SubPNZ *SubStruct `json:",omitzero"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be worth also testing pointer values set with zero-value struct/string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, why not. done.


// test IsZero()bool is repected
Time time.Time `json:",omitzero"`
}

var omitZeroValue = OmitZero{
StrNZ: "str",
PtrNZ: &str,
IntNZ: 6,
SubNZ: SubStruct{Value: "1", Value2: "2"},
SubPNZ: &SubStruct{Value: "3", Value2: "4"},
}

var omitZeroString = "{" +
`"StrNZ":"str",` +
`"PtrNZ":"bla",` +
`"intField":6,` +
`"SubNZ":{"Value":"1","Value2":"2"},` +
`"SubPNZ":{"Value":"3","Value2":"4"}` +
"}"

type Opts struct {
StrNull opt.String
StrEmpty opt.String
Expand Down
26 changes: 26 additions & 0 deletions tests/omitzero.go
Original file line number Diff line number Diff line change
@@ -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"`
}