diff --git a/Makefile b/Makefile index cc5ebbad..012333e8 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,7 @@ generate: build 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 + bin/easyjson -disallow_duplicate_fields ./tests/duplicate_fields.go test: generate go test \ diff --git a/README.md b/README.md index 943b9e4c..94b87710 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ Usage of easyjson: only generate stubs for marshaler/unmarshaler funcs -disallow_unknown_fields return error if some unknown field in json appeared + -disallow_duplicate_fields + return error if a field appears in the json more than once -disable_members_unescape disable unescaping of \uXXXX string sequences in member names ``` diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 5755beea..f5b26f3a 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -32,6 +32,7 @@ type Generator struct { OmitEmpty bool DisallowUnknownFields bool SkipMemberNameUnescaping bool + DisallowDuplicateFields bool OutName string BuildTags string @@ -137,6 +138,9 @@ func (g *Generator) writeMain() (path string, err error) { if g.SkipMemberNameUnescaping { fmt.Fprintln(f, " g.SkipMemberNameUnescaping()") } + if g.DisallowDuplicateFields { + fmt.Fprintln(f, " g.DisallowDuplicateFields()") + } sort.Strings(g.Types) for _, v := range g.Types { diff --git a/easyjson/main.go b/easyjson/main.go index d337c846..b6095934 100644 --- a/easyjson/main.go +++ b/easyjson/main.go @@ -30,6 +30,7 @@ var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output fil var specifiedName = flag.String("output_filename", "", "specify the filename of the output") var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file") var disallowUnknownFields = flag.Bool("disallow_unknown_fields", false, "return error if any unknown field in json appeared") +var disallowDuplicateFields = flag.Bool("disallow_duplicate_fields", false, "return error if a field appears in the json more than once") var skipMemberNameUnescaping = flag.Bool("disable_members_unescape", false, "don't perform unescaping of member names to improve performance") func generate(fname string) (err error) { @@ -78,6 +79,7 @@ func generate(fname string) (err error) { LowerCamelCase: *lowerCamelCase, NoStdMarshalers: *noStdMarshalers, DisallowUnknownFields: *disallowUnknownFields, + DisallowDuplicateFields: *disallowDuplicateFields, SkipMemberNameUnescaping: *skipMemberNameUnescaping, OmitEmpty: *omitEmpty, LeaveTemps: *leaveTemps, diff --git a/gen/decoder.go b/gen/decoder.go index 0a0faa26..f840e1e5 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -343,11 +343,17 @@ func (g *Generator) genStructFieldDecoder(t reflect.Type, f reflect.StructField) } fmt.Fprintf(g.out, " case %q:\n", jsonName) + if g.disallowDuplicateFields { + g.imports["fmt"] = "fmt" + fmt.Fprintf(g.out, " if %sSet {\n", f.Name) + fmt.Fprintf(g.out, " in.AddError(fmt.Errorf(\"duplicate field %s\"))\n", f.Name) + fmt.Fprintf(g.out, " }\n") + } if err := g.genTypeDecoder(f.Type, "out."+f.Name, tags, 3); err != nil { return err } - if tags.required { + if g.disallowDuplicateFields || tags.required { fmt.Fprintf(g.out, "%sSet = true\n", f.Name) } @@ -357,7 +363,7 @@ func (g *Generator) genStructFieldDecoder(t reflect.Type, f reflect.StructField) func (g *Generator) genRequiredFieldSet(t reflect.Type, f reflect.StructField) { tags := parseFieldTags(f) - if !tags.required { + if !g.disallowDuplicateFields && !tags.required { return } diff --git a/gen/generator.go b/gen/generator.go index 79f4d6f7..5a2c1fb2 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -39,6 +39,7 @@ type Generator struct { fieldNamer FieldNamer simpleBytes bool skipMemberNameUnescaping bool + disallowDuplicateFields bool // package path to local alias map for tracking imports imports map[string]string @@ -123,6 +124,11 @@ func (g *Generator) SkipMemberNameUnescaping() { g.skipMemberNameUnescaping = true } +// DisallowDuplicateFields instructs to error when a field is defined more than once +func (g *Generator) DisallowDuplicateFields() { + g.disallowDuplicateFields = true +} + // OmitEmpty triggers `json=",omitempty"` behaviour by default. func (g *Generator) OmitEmpty() { g.omitEmpty = true diff --git a/tests/duplicate_fields.go b/tests/duplicate_fields.go new file mode 100644 index 00000000..c3c3e2c2 --- /dev/null +++ b/tests/duplicate_fields.go @@ -0,0 +1,7 @@ +package tests + +//easyjson:json +type Dupl struct { + A int `json:"a"` + B int `json:"b"` +} diff --git a/tests/duplicate_fields_test.go b/tests/duplicate_fields_test.go new file mode 100644 index 00000000..e21d7d50 --- /dev/null +++ b/tests/duplicate_fields_test.go @@ -0,0 +1,39 @@ +package tests + +import ( + "testing" + + "github.com/mailru/easyjson" +) + +func TestDuplicateFieldError(t *testing.T) { + var d Dupl + err := easyjson.Unmarshal([]byte(`{"a": 1, "a": 2}`), &d) + if err == nil || err.Error() != "duplicate field A" { + t.Error("want error, got ", err) + } +} + +func TestDuplicateFieldNoError(t *testing.T) { + var d Dupl + err := easyjson.Unmarshal([]byte(`{"a": 1, "b": 2}`), &d) + if err != nil { + t.Error("unexpected error ", err) + } + if d != (Dupl{A: 1, B: 2}) { + t.Error("unexpected value ", d) + } +} + +func TestDuplicateFieldNoErrorWithoutOption(t *testing.T) { + // We use the DisallowUnknown struct, which is generated without the duplicate field check enabled, + // so we expect to get no error and the last given value. + var d DisallowUnknown + err := easyjson.Unmarshal([]byte(`{"field_one": "a", "field_one": "b"}`), &d) + if err != nil { + t.Error("unexpected error ", err) + } + if d != (DisallowUnknown{FieldOne: "b"}) { + t.Error("unexpected value ", d) + } +}