Skip to content
Open
Show file tree
Hide file tree
Changes from all 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"`

// 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"`
}