Skip to content

Commit

Permalink
Merge pull request #195 from cmeon/master
Browse files Browse the repository at this point in the history
inception: Reset undefined fields when reusing objects.
  • Loading branch information
pquerna committed Dec 13, 2016
2 parents a29c662 + 65f05c1 commit c24ab9b
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 31 deletions.
12 changes: 6 additions & 6 deletions Makefile
Expand Up @@ -26,11 +26,11 @@ test: ffize test-core
go test -v github.com/pquerna/ffjson/tests/...

ffize: install
ffjson tests/ff.go
ffjson tests/goser/ff/goser.go
ffjson tests/go.stripe/ff/customer.go
ffjson tests/types/ff/everything.go
ffjson tests/number/ff/number.go
ffjson -force-regenerate tests/ff.go
ffjson -force-regenerate tests/goser/ff/goser.go
ffjson -force-regenerate tests/go.stripe/ff/customer.go
ffjson -force-regenerate -reset-fields tests/types/ff/everything.go
ffjson -force-regenerate tests/number/ff/number.go

bench: ffize all
go test -v -benchmem -bench MarshalJSON github.com/pquerna/ffjson/tests
Expand All @@ -39,6 +39,6 @@ bench: ffize all

clean:
go clean -i github.com/pquerna/ffjson/...
rm -f tests/*/ff/*_ffjson.go tests/*_ffjson.go
rm -rf tests/ff/*_ffjson.go tests/*_ffjson.go tests/ffjson-inception*

.PHONY: deps clean test fmt install all
9 changes: 5 additions & 4 deletions ffjson.go
Expand Up @@ -29,10 +29,11 @@ import (
"regexp"
)

var outputPathFlag = flag.String("w", "", "Write generate code to this path instead of ${input}_ffjson.go.")
var goCmdFlag = flag.String("go-cmd", "", "Path to go command; Useful for `goapp` support.")
var importNameFlag = flag.String("import-name", "", "Override import name in case it cannot be detected.")
var outputPathFlag = flag.String("w", "", "Write generate code to this path instead of ${input}_ffjson.go.")
var goCmdFlag = flag.String("go-cmd", "", "Path to go command; Useful for `goapp` support.")
var importNameFlag = flag.String("import-name", "", "Override import name in case it cannot be detected.")
var forceRegenerateFlag = flag.Bool("force-regenerate", false, "Regenerate every input file, without checking modification date.")
var resetFields = flag.Bool("reset-fields", false, "When unmarshalling reset all fields missing in the JSON")

func usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
Expand Down Expand Up @@ -73,7 +74,7 @@ func main() {
importName = *importNameFlag
}

err := generator.GenerateFiles(goCmd, inputPath, outputPath, importName, *forceRegenerateFlag)
err := generator.GenerateFiles(goCmd, inputPath, outputPath, importName, *forceRegenerateFlag, *resetFields)

if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s:\n\n", err)
Expand Down
6 changes: 3 additions & 3 deletions generator/generator.go
Expand Up @@ -23,10 +23,10 @@ import (
"os"
)

func GenerateFiles(goCmd string, inputPath string, outputPath string, importName string, forceRegenerate bool) error {
func GenerateFiles(goCmd string, inputPath string, outputPath string, importName string, forceRegenerate bool, resetFields bool) error {

if _, StatErr := os.Stat(outputPath); !os.IsNotExist(StatErr) {
inputFileInfo, inputFileErr := os.Stat(inputPath)
inputFileInfo, inputFileErr := os.Stat(inputPath)
outputFileInfo, outputFileErr := os.Stat(outputPath)

if nil == outputFileErr && nil == inputFileErr {
Expand All @@ -43,7 +43,7 @@ func GenerateFiles(goCmd string, inputPath string, outputPath string, importName
return err
}

im := NewInceptionMain(goCmd, inputPath, outputPath)
im := NewInceptionMain(goCmd, inputPath, outputPath, resetFields)

err = im.Generate(packageName, structs, importName)
if err != nil {
Expand Down
16 changes: 10 additions & 6 deletions generator/inceptionmain.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions inception/decoder.go
Expand Up @@ -52,6 +52,7 @@ func CreateUnmarshalJSON(ic *Inception, si *StructInfo) error {
SI: si,
IC: ic,
ValidValues: validValues,
ResetFields: ic.ResetFields,
})

ic.OutputFuncs = append(ic.OutputFuncs, out)
Expand Down Expand Up @@ -227,20 +228,20 @@ sliceOrArray:

if typ.Kind() == reflect.Array {
return tplStr(decodeTpl["handleArray"], handleArray{
IC: ic,
Name: name,
Typ: typ,
IC: ic,
Name: name,
Typ: typ,
IsPtr: ptr,
Ptr: reflect.Ptr,
Ptr: reflect.Ptr,
})
}

return tplStr(decodeTpl["handleSlice"], handleArray{
IC: ic,
Name: name,
Typ: typ,
IC: ic,
Name: name,
Typ: typ,
IsPtr: ptr,
Ptr: reflect.Ptr,
Ptr: reflect.Ptr,
})
}

Expand Down
42 changes: 39 additions & 3 deletions inception/decoder_tpl.go
Expand Up @@ -19,6 +19,7 @@ package ffjsoninception

import (
"reflect"
"strconv"
"text/template"
)

Expand Down Expand Up @@ -278,7 +279,7 @@ type handleArray struct {
Typ reflect.Type
Ptr reflect.Kind
UseReflectToSet bool
IsPtr bool
IsPtr bool
}

var handleArrayTxt = `
Expand Down Expand Up @@ -521,6 +522,7 @@ type ujFunc struct {
IC *Inception
SI *StructInfo
ValidValues []string
ResetFields bool
}

var ujFuncTxt = `
Expand All @@ -539,6 +541,12 @@ func (uj *{{.SI.Name}}) UnmarshalJSONFFLexer(fs *fflib.FFLexer, state fflib.FFPa
tok := fflib.FFTok_init
wantedTok := fflib.FFTok_init
{{if eq .ResetFields true}}
{{range $index, $field := $si.Fields}}
var ffj_set_{{$si.Name}}_{{$field.Name}} = false
{{end}}
{{end}}
mainparse:
for {
tok = fs.Scan()
Expand Down Expand Up @@ -635,11 +643,13 @@ mainparse:
}
}
}
{{range $index, $field := $si.Fields}}
handle_{{$field.Name}}:
{{with $fieldName := $field.Name | printf "uj.%s"}}
{{handleField $ic $fieldName $field.Typ $field.Pointer $field.ForceString}}
{{if eq $.ResetFields true}}
ffj_set_{{$si.Name}}_{{$field.Name}} = true
{{end}}
state = fflib.FFParse_after_value
goto mainparse
{{end}}
Expand All @@ -659,9 +669,35 @@ tokerror:
}
panic("ffjson-generated: unreachable, please report bug.")
done:
{{if eq .ResetFields true}}
{{range $index, $field := $si.Fields}}
if !ffj_set_{{$si.Name}}_{{$field.Name}} {
{{with $fieldName := $field.Name | printf "uj.%s"}}
{{if eq $field.Pointer true}}
{{$fieldName}} = nil
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Interface), 10) + `}}
{{$fieldName}} = nil
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Slice), 10) + `}}
{{$fieldName}} = nil
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Array), 10) + `}}
{{$fieldName}} = [{{$field.Typ.Len}}]{{getType $ic $fieldName $field.Typ.Elem}}{}
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Map), 10) + `}}
{{$fieldName}} = nil
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Bool), 10) + `}}
{{$fieldName}} = false
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.String), 10) + `}}
{{$fieldName}} = ""
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Struct), 10) + `}}
{{$fieldName}} = {{getType $ic $fieldName $field.Typ}}{}
{{else}}
{{$fieldName}} = {{getType $ic $fieldName $field.Typ}}(0)
{{end}}
{{end}}
}
{{end}}
{{end}}
return nil
}
`

type handleUnmarshaler struct {
Expand Down
4 changes: 3 additions & 1 deletion inception/inception.go
Expand Up @@ -36,16 +36,18 @@ type Inception struct {
OutputImports map[string]bool
OutputFuncs []string
q ConditionalWrite
ResetFields bool
}

func NewInception(inputPath string, packageName string, outputPath string) *Inception {
func NewInception(inputPath string, packageName string, outputPath string, resetFields bool) *Inception {
return &Inception{
objs: make([]*StructInfo, 0),
InputPath: inputPath,
OutputPath: outputPath,
PackageName: packageName,
OutputFuncs: make([]string, 0),
OutputImports: make(map[string]bool),
ResetFields: resetFields,
}
}

Expand Down
50 changes: 50 additions & 0 deletions tests/types/types_test.go
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
ff "github.com/pquerna/ffjson/tests/types/ff"
"reflect"
"strings"
"testing"
)

Expand Down Expand Up @@ -135,3 +136,52 @@ func TestUnmarshalNullPointer(t *testing.T) {
t.Fatalf("record.Something decoding problem, expected: nil got: %v", record.FooStruct)
}
}

func TestUnmarshalToReusedObject(t *testing.T) {
JSONParts := []string{
`"Bool":true`,
`"Int":1`,
`"Int8": 2`,
`"Int16": 3`,
`"Int32": -4`,
`"Int64": 57`,
`"Uint": 100`,
`"Uint8": 101`,
`"Uint16": 102`,
`"Uint32": 50`,
`"Uint64": 103`,
`"Uintptr": 104`,
`"Float32": 3.14`,
`"Float64": 3.15`,
`"Array": [1,2,3]`,
`"Map": {"bar": 2,"foo": 1}`,
`"String": "snowman☃\uD801\uDC37"`,
`"StringPointer": "pointed snowman☃\uD801\uDC37"`,
`"Int64Pointer": 44`,
`"FooStruct": {"Bar": 1}`,
`"MapMap": {"a0": {"b0":"foo"}, "a1":{"a2":"bar"}}`,
`"MapArraySlice": {"foo":[[1,2,3],[4,5,6],[7]], "bar": [[1,2,3,4],[5,6,7]]}`,
`"Something": 99`,
}

JSONWhole := "{" + strings.Join(JSONParts, ",") + "}"
var record ff.Everything
if err := record.UnmarshalJSON([]byte(JSONWhole)); err != nil {
t.Fatalf("UnmarshalJSON: %v", err)
}

for _, part := range JSONParts {
reuseRecord := record
if err := reuseRecord.UnmarshalJSON([]byte("{" + part + "}")); err != nil {
t.Fatalf("UnmarshalJSON: %v", err)
}
var emptyRecord ff.Everything
if err := emptyRecord.UnmarshalJSON([]byte("{" + part + "}")); err != nil {
t.Fatalf("UnmarshalJSON: %v", err)
}

if !reflect.DeepEqual(reuseRecord, emptyRecord) {
t.Errorf("%#v should be equal to %#v", reuseRecord, emptyRecord)
}
}
}

0 comments on commit c24ab9b

Please sign in to comment.