gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit c61d0f5277a4c1615a93d31fb400d354f72ec348
parent f2cc13a668caf474b5d5806c7f1adbbe4ce28524
Author: francoispqt <francois@parquet.ninja>
Date:   Thu, 17 Jan 2019 10:59:10 +0800

Merge branch 'master' of https://github.com/adrianwit/gojay into adrianwit-master

Diffstat:
Acodegen/README.md | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/field.go | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/generator.go | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/generator_test.go | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/helper.go | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/options.go | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/struct.go | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/template.go | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/template_test.go | 19+++++++++++++++++++
Acodegen/test/annotated_struct/encoding.go | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/annotated_struct/encoding_test.go | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/annotated_struct/message.go | 19+++++++++++++++++++
Acodegen/test/annotated_struct/sub_message.go | 10++++++++++
Acodegen/test/basic_struct/encoding.go | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/basic_struct/encoding_test.go | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/basic_struct/message.go | 18++++++++++++++++++
Acodegen/test/basic_struct/sub_message.go | 10++++++++++
Acodegen/test/embedded_struct/encoding.go | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/embedded_struct/encoding_test.go | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/embedded_struct/message.go | 22++++++++++++++++++++++
Acodegen/test/embedded_struct/sub_message.go | 9+++++++++
Acodegen/test/pooled_struct/encoding.go | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/pooled_struct/encoding_test.go | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acodegen/test/pooled_struct/message.go | 18++++++++++++++++++
Acodegen/test/pooled_struct/sub_message.go | 10++++++++++
Agojaygen/gojaygen | 0
Agojaygen/gojaygen.go | 22++++++++++++++++++++++
27 files changed, 3154 insertions(+), 0 deletions(-)

diff --git a/codegen/README.md b/codegen/README.md @@ -0,0 +1,65 @@ +# Gojay code generator + +This package provides a command line tool to generate gojay's marshaling and unmarshing interface implementation for custom type(s) + + +## Get started + +```sh +go install github.com/francoispqt/gojay/gojaygen +``` + +## Generate code + +### Basic command + +The basic command is straightforward and easy to use: +```sh +cd $GOPATH/src/mycoany/myproject +gojaygen -p myproject +``` +If you just want to the output to stdout, omit the third parameter. + +### Using flags + +- s file/dir to path, can be a relative or absolute path +- t root types to generate with its all dependencies (comma separated) +- a annotation tag used to read meta data (default: json) +- o output file (relative or absolute path) +- p reuse object witt sync.Pool + +Examples: + +- Specific type in a go package, write to a file: +```sh +gojay -s /tmp/myproj -t SomeType -o output.go +``` + +- Specific types in a go package, with custom tag, write to a file:: +```sh +gojay -s somegofile.go -a gojay -t SomeType +``` + + +## Generator tags + +You can add tags to your structs to control: + +- the JSON key +- skip a struct field +- the use of omit empty methods for marshaling +- timeFormat (java style data format) +- timeLayout (golang time layout) + + +### Example: +```go +type A struct { + Str string `json:"string"` + StrOmitEmpty string `json:"stringOrEmpty,omitempty"` + Skip string `json:"-"` + StartTime time.Time `json:"startDate" timeFormat:"yyyy-MM-dd HH:mm:ss"` + EndTime *time.Time `json:"endDate" timeLayout:"2006-01-02 15:04:05"` +} +``` + diff --git a/codegen/field.go b/codegen/field.go @@ -0,0 +1,161 @@ +package codegen + +import ( + "fmt" + "github.com/viant/toolbox" + "strings" +) + +//Field represents a field. +type Field struct { + Key string + Init string + OmitEmpty string + TimeLayout string + Name string + Accessor string + Mutator string + Receiver string //alias and type name + Alias string //object alias name + Var string //variable for this field + Type string + RawType string + HelperType string + ComponentType string + RawComponentType string + IsPointerComponent bool + + PointerModifier string //takes field pointer, "&" if field is not a pointer type + DereferenceModifier string //take pointer value, i.e "*" if field has a pointer type + + ComponentPointerModifier string //takes item pointer if needed,i.e + ComponentDereferenceModifier string //de reference value if needed, i.e + ComponentInitModifier string //takes item pointer if type is not a pointer type + ComponentInit string //initialises component type + + DecodingMethod string + EncodingMethod string + PoolName string //pool name associated with this field + ResetDependency string + Reset string + IsAnonymous bool + IsPointer bool + IsSlice bool +} + +//NewField returns a new field +func NewField(owner *Struct, field *toolbox.FieldInfo, fieldType *toolbox.TypeInfo) (*Field, error) { + typeName := normalizeTypeName(field.TypeName) + var result = &Field{ + IsAnonymous: field.IsAnonymous, + Name: field.Name, + RawType: field.TypeName, + IsPointer: field.IsPointer, + Key: getJSONKey(owner.options, field), + Receiver: owner.Alias + " *" + owner.TypeInfo.Name, + Type: typeName, + Mutator: owner.Alias + "." + field.Name, + Accessor: owner.Alias + "." + field.Name, + ComponentType: field.ComponentType, + IsPointerComponent: field.IsPointerComponent, + Var: firstLetterToLowercase(field.Name), + Init: fmt.Sprintf("%v{}", typeName), + TimeLayout: "time.RFC3339", + IsSlice: field.IsSlice, + PoolName: getPoolName(field.TypeName), + Alias: owner.Alias, + Reset: "nil", + } + var err error + if field.IsPointer { + result.DereferenceModifier = "*" + result.Init = "&" + result.Init + } else { + result.PointerModifier = "&" + + } + if field.IsSlice { + result.HelperType = getSliceHelperTypeName(field.ComponentType, field.IsPointerComponent) + result.PoolName = getPoolName(field.ComponentType) + } else if fieldType != nil { + result.HelperType = getSliceHelperTypeName(fieldType.Name, field.IsPointerComponent) + } + + if options := getTagOptions(field.Tag, "timeLayout"); len(options) > 0 { + result.TimeLayout = wrapperIfNeeded(options[0], `"`) + } else if options := getTagOptions(field.Tag, "timeFormat"); len(options) > 0 { + result.TimeLayout = wrapperIfNeeded(toolbox.DateFormatToLayout(options[0]), `"`) + } + if strings.Contains(field.Tag, "omitEmpty") { + result.OmitEmpty = "OmitEmpty" + } + if owner.options.PoolObjects { + if field.IsPointer && !strings.HasSuffix(field.TypeName, ".Time") { + poolName := getPoolName(field.TypeName) + result.Init = fmt.Sprintf(`%v.Get().(*%v)`, poolName, field.TypeName) + } + } + + encodingMethod := field.ComponentType + if encodingMethod == "" { + encodingMethod = result.Type + } + result.DecodingMethod = firstLetterToUppercase(encodingMethod) + result.EncodingMethod = firstLetterToUppercase(encodingMethod) + + switch typeName { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": + result.Reset = "0" + case "float32", "float64": + result.Reset = "0.0" + case "string": + result.Reset = `""` + + case "bool": + result.Reset = "false" + default: + if field.IsSlice && owner.Type(field.ComponentType) != nil { + var itemPointer = "" + if !field.IsPointerComponent { + itemPointer = "&" + } + result.ResetDependency, err = expandFieldTemplate(poolSliceInstanceRelease, struct { + PoolName string + Accessor string + PointerModifier string + }{PoolName: result.PoolName, Accessor: result.Accessor, PointerModifier: itemPointer}) + if err != nil { + return nil, err + } + + } else if field.IsPointer && fieldType != nil { + result.ResetDependency, err = expandFieldTemplate(poolInstanceRelease, struct { + PoolName string + Accessor string + }{PoolName: result.PoolName, Accessor: result.Accessor}) + if err != nil { + return nil, err + } + } + + } + if field.IsSlice || field.IsPointer { + result.Reset = "nil" + } + + if result.IsPointerComponent { + result.ComponentInit = "&" + result.ComponentType + "{}" + result.RawComponentType = "*" + result.ComponentType + + result.ComponentDereferenceModifier = "*" + result.ComponentInitModifier = "&" + + } else { + result.ComponentInit = result.ComponentType + "{}" + result.RawComponentType = result.ComponentType + + result.ComponentPointerModifier = "&" + } + + return result, nil +} diff --git a/codegen/generator.go b/codegen/generator.go @@ -0,0 +1,167 @@ +package codegen + +import ( + "fmt" + "github.com/viant/toolbox" + "io/ioutil" + "strings" +) + +type Generator struct { + fileInfo *toolbox.FileSetInfo + types map[string]string + structTypes map[string]string + sliceTypes map[string]string + pooledObjects map[string]string + poolInit map[string]string + imports map[string]bool + filedInit []string + Pkg string + Code string + Init string + Imports string + options *Options +} + +func (g *Generator) Type(typeName string) *toolbox.TypeInfo { + return g.fileInfo.Type(typeName) +} + +func (g *Generator) addImport(pkg string) { + g.imports[`"`+pkg+`"`] = true +} + +func (g *Generator) init() { + g.filedInit = []string{} + g.imports = map[string]bool{} + g.pooledObjects = map[string]string{} + g.structTypes = map[string]string{} + g.sliceTypes = map[string]string{} + g.poolInit = map[string]string{} + g.addImport("github.com/francoispqt/gojay") +} + +func (g *Generator) Generate(options *Options) error { + if err := options.Validate(); err != nil { + return err + } + g.options = options + g.init() + + if options.PoolObjects { + g.addImport("sync") + } + if err := g.readPackageCode(options.Source); err != nil { + return err + } + for _, rootType := range options.Types { + if err := g.generateStructCode(rootType); err != nil { + return err + } + } + g.Imports = strings.Join(toolbox.MapKeysToStringSlice(g.imports), "\n") + return g.writeCode() +} + +func (g *Generator) writeCode() error { + var generatedCode = []string{} + for _, code := range g.pooledObjects { + generatedCode = append(generatedCode, code) + } + generatedCode = append(generatedCode, "") + for _, code := range g.sliceTypes { + generatedCode = append(generatedCode, code) + } + generatedCode = append(generatedCode, "") + for _, code := range g.structTypes { + generatedCode = append(generatedCode, code) + } + + for _, code := range g.poolInit { + if g.Init != "" { + g.Init += "\n" + } + g.Init += code + } + g.Code = strings.Join(generatedCode, "\n") + + expandedCode, err := expandBlockTemplate(fileCode, g) + if err != nil { + return err + } + if g.options.Dest == "" { + fmt.Print(expandedCode) + return nil + } + return ioutil.WriteFile(g.options.Dest, []byte(expandedCode), 0644) +} + +func (g *Generator) generatePrimitiveArray(field *Field) error { + key := field.ComponentType + toolbox.AsString(field.IsPointerComponent) + if _, ok := g.sliceTypes[key]; ok { + return nil + } + code, err := expandBlockTemplate(baseTypeSlice, field) + g.sliceTypes[key] = code + return err +} + +func (g *Generator) generateObjectArray(field *Field) error { + if _, ok := g.sliceTypes[field.RawComponentType]; ok { + return nil + } + + if err := g.generateStructCode(field.ComponentType); err != nil { + return err + } + code, err := expandBlockTemplate(structTypeSlice, field) + if err != nil { + return err + } + g.sliceTypes[field.RawComponentType] = code + return err +} + +func (g *Generator) generatePool(structType string) error { + if !g.options.PoolObjects { + return nil + } + var err error + if g.pooledObjects[structType], err = expandBlockTemplate(poolVar, struct { + PoolName string + }{getPoolName(structType)}); err == nil { + g.poolInit[structType], err = expandBlockTemplate(poolInit, struct { + PoolName string + Type string + }{getPoolName(structType), structType}) + } + return err + +} + +func (g *Generator) generateStructCode(structType string) error { + structType = normalizeTypeName(structType) + typeInfo := g.Type(structType) + if typeInfo == nil { + return nil + } + if _, hasCode := g.structTypes[structType]; hasCode { + return nil + } + g.generatePool(structType) + aStruct := NewStruct(typeInfo, g) + code, err := aStruct.Generate() + if err != nil { + return err + } + g.structTypes[structType] = code + return nil +} + +func (g *Generator) readPackageCode(pkgPath string) error { + var err error + fragments := strings.Split(pkgPath, "/") + g.Pkg = fragments[len(fragments)-1] + g.fileInfo, err = toolbox.NewFileSetInfo(pkgPath) + return err +} diff --git a/codegen/generator_test.go b/codegen/generator_test.go @@ -0,0 +1,72 @@ +package codegen + +import ( + "github.com/stretchr/testify/assert" + "github.com/viant/toolbox" + "log" + "path" + "testing" +) + +func TestGenerator_Generate(t *testing.T) { + + parent := path.Join(toolbox.CallerDirectory(3), "test") + + var useCases = []struct { + description string + options *Options + hasError bool + }{ + { + description: "basic struct code generation", + options: &Options{ + Source: path.Join(parent, "basic_struct"), + Types: []string{"Message"}, + Dest: path.Join(parent, "basic_struct", "encoding.go"), + }, + }, + + { + description: "struct with pool code generation", + options: &Options{ + Source: path.Join(parent, "pooled_struct"), + Types: []string{"Message"}, + Dest: path.Join(parent, "pooled_struct", "encoding.go"), + PoolObjects: true, + }, + }, + { + description: "struct with embedded type code generation", + options: &Options{ + Source: path.Join(parent, "embedded_struct"), + Types: []string{"Message"}, + Dest: path.Join(parent, "embedded_struct", "encoding.go"), + PoolObjects: false, + }, + }, + { + description: "struct with json annotation and time/foarmat|layouat generation", + options: &Options{ + Source: path.Join(parent, "annotated_struct"), + Types: []string{"Message"}, + Dest: path.Join(parent, "annotated_struct", "encoding.go"), + PoolObjects: false, + TagName: "json", + }, + }, + } + + for _, useCase := range useCases { + gen := &Generator{} + err := gen.Generate(useCase.options) + if useCase.hasError { + assert.NotNil(t, err, useCase.description) + continue + } + if !assert.Nil(t, err, useCase.description) { + log.Fatal(err) + continue + } + } + +} diff --git a/codegen/helper.go b/codegen/helper.go @@ -0,0 +1,88 @@ +package codegen + +import ( + "github.com/viant/toolbox" + "reflect" + "strings" +) + +func firstLetterToUppercase(text string) string { + return strings.ToUpper(string(text[0:1])) + string(text[1:]) +} + +func firstLetterToLowercase(text string) string { + return strings.ToLower(string(text[0:1])) + string(text[1:]) +} + +func extractReceiverAlias(structType string) string { + var result = string(structType[0]) + for i := len(structType) - 1; i > 0; i-- { + aChar := string(structType[i]) + lowerChar := strings.ToLower(aChar) + if lowerChar != aChar { + result = lowerChar + break + } + } + return strings.ToLower(result) +} + +func getTagOptions(tag, key string) []string { + if tag == "" { + return nil + } + var structTag = reflect.StructTag(strings.Replace(tag, "`", "", len(tag))) + options, ok := structTag.Lookup(key) + if !ok { + return nil + } + return strings.Split(options, ",") +} + +func getSliceHelperTypeName(typeName string, isPointer bool) string { + if typeName == "" { + return "" + } + var pluralName = firstLetterToUppercase(typeName) + "s" + if isPointer { + pluralName += "Ptr" + } + return pluralName +} + +func isSkipable(options *Options, field *toolbox.FieldInfo) bool { + if options := getTagOptions(field.Tag, options.TagName); len(options) > 0 { + for _, candidate := range options { + if candidate == "-" { + return true + } + } + } + return false +} + +func wrapperIfNeeded(text, wrappingChar string) string { + if strings.HasPrefix(text, wrappingChar) { + return text + } + return wrappingChar + text + wrappingChar +} + +func getPoolName(typeName string) string { + typeName = strings.Replace(typeName, "*", "", 1) + return typeName + "Pool" +} + +func getJSONKey(options *Options, field *toolbox.FieldInfo) string { + var key = field.Name + if field.Tag != "" { + if options := getTagOptions(field.Tag, options.TagName); len(options) > 0 { + key = options[0] + } + } + return key +} + +func normalizeTypeName(typeName string) string { + return strings.Replace(typeName, "*", "", strings.Count(typeName, "*")) +} diff --git a/codegen/options.go b/codegen/options.go @@ -0,0 +1,54 @@ +package codegen + +import ( + "flag" + "github.com/go-errors/errors" + "github.com/viant/toolbox" + "github.com/viant/toolbox/url" + "strings" +) + +type Options struct { + Source string + Dest string + Types []string + PoolObjects bool + TagName string +} + +func (o *Options) Validate() error { + if o.Source == "" { + return errors.New("Source was empty") + } + if o.Dest == "" { + return errors.New("Dest was empty") + } + if len(o.Types) == 0 { + return errors.New("Types was empty") + } + return nil +} + +const ( + optionKeySource = "s" + optionKeyDest = "o" + optionKeyTypes = "t" + optionKeyTagName = "a" + optionKeyPoolObjects = "p" +) + +//NewOptionsWithFlagSet creates a new options for the supplide flagset +func NewOptionsWithFlagSet(set *flag.FlagSet) *Options { + toolbox.Dump(set) + + var result = &Options{} + result.Dest = set.Lookup(optionKeyDest).Value.String() + result.Source = set.Lookup(optionKeySource).Value.String() + result.PoolObjects = toolbox.AsBoolean(set.Lookup(optionKeyPoolObjects).Value.String()) + result.TagName = set.Lookup(optionKeyTagName).Value.String() + result.Types = strings.Split(set.Lookup(optionKeyTypes).Value.String(), ",") + if result.Source == "" { + result.Source = url.NewResource(".").ParsedURL.Path + } + return result +} diff --git a/codegen/struct.go b/codegen/struct.go @@ -0,0 +1,296 @@ +package codegen + +import ( + "fmt" + "github.com/viant/toolbox" + "strings" +) + +type Struct struct { + *toolbox.TypeInfo + referenced *toolbox.TypeInfo + *Generator + Alias string + Init string + Body string +} + +//Generate generates decoderCode + structRelease + encoderCode +func (s *Struct) Generate() (string, error) { + return s.generateEncoding(s.TypeInfo) +} + +func (s *Struct) generateEncoding(structInfo *toolbox.TypeInfo) (string, error) { + var initEmbedded, decodingCases, err = s.generateFieldDecoding(structInfo.Fields()) + if err != nil { + return "", err + } + + encodingCases, err := s.generateFieldEncoding(structInfo.Fields()) + if err != nil { + return "", err + } + var resetCode = "" + if s.options.PoolObjects { + resetCode, err = s.generateReset(structInfo.Fields()) + if err != nil { + return "", err + } + } + var data = struct { + Receiver string + Alias string + InitEmbedded string + EncodingCases string + DecodingCases string + Reset string + FieldCount int + }{ + Receiver: s.Alias + " *" + s.Name, + DecodingCases: strings.Join(decodingCases, "\n"), + EncodingCases: strings.Join(encodingCases, "\n"), + FieldCount: len(decodingCases), + InitEmbedded: initEmbedded, + Reset: resetCode, + Alias: s.Alias, + } + return expandBlockTemplate(encodingStructType, data) +} + +func (s *Struct) generateReset(fields []*toolbox.FieldInfo) (string, error) { + fieldReset, err := s.generateFieldReset(fields) + if err != nil { + return "", nil + } + return expandBlockTemplate(resetStruct, struct { + Reset string + Receiver string + }{ + Reset: strings.Join(fieldReset, "\n"), + Receiver: s.Alias + " *" + s.Name, + }) +} + +func (s *Struct) generateFieldReset(fields []*toolbox.FieldInfo) ([]string, error) { + fieldReset := []string{} + for i := range fields { + var templateKey = -1 + fieldTypeInfo := s.Type(normalizeTypeName(fields[i].TypeName)) + field, err := NewField(s, fields[i], fieldTypeInfo) + if err != nil { + return nil, err + } + if field.IsPointer || field.IsSlice || (fieldTypeInfo != nil && fieldTypeInfo.IsSlice) { + templateKey = resetFieldValue + } else { + switch field.Type { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "string", "bool", "[]string", "[]bool", "[]int", "[]int8", "[]int16", "[]int32", "[]int64", "[]uint", "[]uint8", "[]uint16", "[]uint32", "[]uint64", "[]float32", "[]float64", "[]byte": + templateKey = resetFieldValue + } + } + if templateKey != -1 { + code, err := expandFieldTemplate(templateKey, field) + if err != nil { + return nil, err + } + fieldReset = append(fieldReset, code) + } + } + return fieldReset, nil +} + +func (s *Struct) generateFieldDecoding(fields []*toolbox.FieldInfo) (string, []string, error) { + fieldCases := []string{} + var initCode = "" + for i := range fields { + if isSkipable(s.options, fields[i]) { + continue + } + var templateKey = -1 + fieldTypeInfo := s.Type(normalizeTypeName(fields[i].TypeName)) + field, err := NewField(s, fields[i], fieldTypeInfo) + if err != nil { + return "", nil, err + } + if fieldTypeInfo != nil { + if err = s.generateStructCode(fieldTypeInfo.Name); err != nil { + return "", nil, err + } + } + + if field.IsAnonymous { + if fieldTypeInfo != nil { + if field.IsPointer { + init, err := expandBlockTemplate(embeddedStructInit, field) + if err != nil { + return "", nil, err + } + initCode += init + } + init, embeddedCases, err := s.generateFieldDecoding(fieldTypeInfo.Fields()) + if err != nil { + return "", nil, err + } + initCode += init + fieldCases = append(fieldCases, embeddedCases...) + } + continue + } + + main: + switch field.Type { + case "string", "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64": + templateKey = decodeBaseType + case "[]string", "[]bool", "[]int", "[]int8", "[]int16", "[]int32", "[]int64", "[]uint", "[]uint8", "[]uint16", "[]uint32", "[]uint64", "[]float32", "[]float64": + templateKey = decodeBaseTypeSlice + s.generatePrimitiveArray(field) + case "[]byte": + templateKey = decodeRawType + default: + + if fieldTypeInfo != nil { + if !(field.IsSlice || fieldTypeInfo.IsSlice) { + + templateKey = decodeStruct + break main + } + + switch fieldTypeInfo.ComponentType { + case "byte": + templateKey = decodeRawType + break main + + case "string", "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64": + s.generatePrimitiveArray(field) + templateKey = decodeBaseTypeSlice + break main + + } + + if err = s.generateStructCode(field.ComponentType); err != nil { + return "", nil, err + } + + templateKey = decodeStructSlice + if err = s.generateObjectArray(field); err != nil { + return "", nil, err + } + + break main + } else if field.IsSlice { + templateKey = decodeStructSlice + if err = s.generateObjectArray(field); err != nil { + return "", nil, err + } + + } else if strings.HasSuffix(field.Type, "time.Time") { + templateKey = decodeTime + s.addImport("time") + } else { + templateKey = decodeUnknown + } + } + if templateKey != -1 { + decodingCase, err := expandFieldTemplate(templateKey, field) + if err != nil { + return "", nil, err + } + fieldCases = append(fieldCases, decodingCase) + } + + } + return initCode, fieldCases, nil +} + +func (s *Struct) generateEmbeddedFieldEncoding(field *Field, fieldTypeInfo *toolbox.TypeInfo) ([]string, error) { + var result = []string{} + if fieldTypeInfo != nil { + embeddedCases, err := s.generateFieldEncoding(fieldTypeInfo.Fields()) + if err != nil { + return nil, err + } + if field.IsPointer { + result = append(result, fmt.Sprintf(" if %v != nil {", field.Accessor)) + for _, code := range embeddedCases { + result = append(result, " "+code) + } + result = append(result, " }") + } else { + result = append(result, embeddedCases...) + } + } + return result, nil +} + +func (s *Struct) generateFieldEncoding(fields []*toolbox.FieldInfo) ([]string, error) { + fieldCases := []string{} + for i := range fields { + if isSkipable(s.options, fields[i]) { + continue + } + var templateKey = -1 + fieldTypeInfo := s.Type(normalizeTypeName(fields[i].TypeName)) + field, err := NewField(s, fields[i], fieldTypeInfo) + if err != nil { + return nil, err + } + if field.IsAnonymous { + embedded, err := s.generateEmbeddedFieldEncoding(field, fieldTypeInfo) + if err != nil { + return nil, err + } + fieldCases = append(fieldCases, embedded...) + continue + } + main: + switch field.Type { + case "string", "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64": + templateKey = encodeBaseType + case "[]string", "[]bool", "[]int", "[]int8", "[]int16", "[]int32", "[]int64", "[]uint", "[]uint8", "[]uint16", "[]uint32", "[]uint64", "[]float32", "[]float64": + templateKey = encodeBaseTypeSlice + s.generatePrimitiveArray(field) + case "[]byte": + templateKey = encodeRawType + default: + if fieldTypeInfo != nil { + if !(field.IsSlice || fieldTypeInfo.IsSlice) { + templateKey = encodeStruct + break main + } + switch fieldTypeInfo.ComponentType { + case "byte": + templateKey = encodeRawType + break main + case "string", "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64": + templateKey = decodeBaseTypeSlice + break main + } + templateKey = encodeStructSlice + break main + } else if field.IsSlice { + templateKey = encodeStructSlice + } else if strings.HasSuffix(field.Type, "time.Time") { + templateKey = encodeTime + } else { + templateKey = encodeUnknown + } + } + if templateKey != -1 { + decodingCase, err := expandFieldTemplate(templateKey, field) + if err != nil { + return nil, err + } + fieldCases = append(fieldCases, decodingCase) + } + + } + return fieldCases, nil +} + +func NewStruct(info *toolbox.TypeInfo, generator *Generator) *Struct { + return &Struct{ + TypeInfo: info, + Generator: generator, + Alias: extractReceiverAlias(info.Name), + } +} diff --git a/codegen/template.go b/codegen/template.go @@ -0,0 +1,272 @@ +package codegen + +import ( + "bytes" + "fmt" + "text/template" +) + +const ( + decodeBaseType = iota + encodeBaseType + decodeBaseTypeSlice + encodeBaseTypeSlice + decodeRawType + encodeRawType + decodeStruct + encodeStruct + + decodeStructSlice + encodeStructSlice + decodeTime + encodeTime + + decodeUnknown + encodeUnknown + + resetFieldValue + poolInstanceRelease + poolSliceInstanceRelease +) + +var fieldTemplate = map[int]string{ + decodeBaseType: ` case "{{.Key}}": +{{if .IsPointer}} var value {{.Type}} + err := dec.{{.DecodingMethod}}(&value) + if err == nil { + {{.Accessor}} = &value + } + return err +{{else}} return dec.{{.DecodingMethod}}(&{{.Accessor}}){{end}} +`, + encodeBaseType: ` enc.{{.EncodingMethod}}Key{{.OmitEmpty}}("{{.Key}}", {{.DereferenceModifier}}{{.Accessor}})`, + + decodeBaseTypeSlice: ` case "{{.Key}}": + var aSlice = {{.HelperType}}{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + {{.Mutator}} = {{.RawType}}(aSlice) + } + return err +`, + encodeBaseTypeSlice: ` var {{.Var}}Slice = {{.HelperType}}({{.Accessor}}) + enc.ArrayKey{{.OmitEmpty}}("{{.Key}}",{{.Var}}Slice)`, + + decodeRawType: ` case "{{.Key}}": + var value = gojay.EmbeddedJSON{} + err := dec.AddEmbeddedJSON(&value) + if err == nil && len(value) > 0 { + {{.Mutator}} = {{.Type}}(value) + } + return err +`, + + encodeRawType: ` var {{.Var}}Slice = gojay.EmbeddedJSON({{.Accessor}}) + enc.AddEmbeddedJSONKey{{.OmitEmpty}}("{{.Key}}", &{{.Var}}Slice)`, + decodeStruct: ` case "{{.Key}}":{{if .IsPointer}} + var value = {{.Init}} + err := dec.Object(value) + if err == nil { + {{.Mutator}} = value + } +{{else}} + err := dec.Object(&{{.Mutator}}) +{{end}} + return err +`, + encodeStruct: ` enc.ObjectKey{{.OmitEmpty}}("{{.Key}}", {{.PointerModifier}}{{.Accessor}})`, + + decodeStructSlice: ` case "{{.Key}}": + var aSlice = {{.HelperType}}{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + {{.Mutator}} = {{.RawType}}(aSlice) + } + return err + `, + + encodeStructSlice: ` var {{.Var}}Slice = {{.HelperType}}({{.Accessor}}) + enc.ArrayKey{{.OmitEmpty}}("{{.Key}}", {{.DereferenceModifier}}{{.Var}}Slice)`, + + decodeTime: ` case "{{.Key}}": + var format = {{.TimeLayout}} + var value = {{.Init}} + err := dec.DecodeTime({{.PointerModifier}}value, format){{if .IsPointer}} + if err == nil { + {{.Mutator}} = value + }{{end}} + return err +`, + + encodeTime: `{{if .IsPointer}} if {{.Accessor}} != nil { + enc.TimeKey("{{.Key}}", {{.PointerModifier}}{{.Accessor}}, {{.TimeLayout}}) + }{{else}} enc.TimeKey("{{.Key}}", {{.PointerModifier}}{{.Accessor}}, {{.TimeLayout}}){{end}}`, + decodeUnknown: ` case "{{.Key}}": + //TODO + //dec.Any({{.Accessor}}) + return nil +`, + encodeUnknown: ` + //TODO + //enc.Any({{.Accessor}}) + +`, + resetFieldValue: `{{if .ResetDependency}}{{.ResetDependency}} +{{end}} {{.Mutator}} = {{.Reset}}`, + poolInstanceRelease: ` {{.PoolName}}.Put({{.Accessor}})`, + + poolSliceInstanceRelease: ` for i := range {{.Accessor}} { + {{.Accessor}}[i].Reset() + {{.PoolName}}.Put({{.PointerModifier}}{{.Accessor}}[i]) + }`, +} + +const ( + fileCode = iota + encodingStructType + baseTypeSlice + structTypeSlice + resetStruct + poolVar + poolInit + embeddedStructInit +) + +var blockTemplate = map[int]string{ + fileCode: `// Code generated by GoJayGen. DO NOT EDIT.\n\n +package {{.Pkg}} + +import ( +{{.Imports}} +) + +{{if .Init}} +func init() { +{{.Init}} +} +{{end}} + +{{.Code}} + +`, + encodingStructType: ` + +//MarshalJSONObject implements MarshalerJSONObject +func ({{.Receiver}}) MarshalJSONObject(enc *gojay.Encoder) { +{{.EncodingCases}} +} + +//IsNil checks if instance is nil +func ({{.Receiver}}) IsNil() bool { + return {{.Alias}} == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func ({{.Receiver}}) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { +{{.InitEmbedded}} + switch k { +{{.DecodingCases}} + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func ({{.Receiver}}) NKeys() int { return {{.FieldCount}} } + +{{.Reset}} + +`, + + baseTypeSlice: ` + +type {{.HelperType}} {{.RawType}} + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *{{.HelperType}}) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value {{.ComponentType}} + if err := dec.{{.DecodingMethod}}(&value); err != nil { + return err + } + *a = append(*a, {{.ComponentInitModifier}}value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a {{.HelperType}}) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.{{.EncodingMethod}}({{.ComponentDereferenceModifier}}item) + } +} + +//IsNil checks if array is nil +func (a {{.HelperType}}) IsNil() bool { + return len(a) == 0 +} +`, + + structTypeSlice: ` +type {{.HelperType}} {{.RawType}} + +func (s *{{.HelperType}}) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = {{.ComponentInit}} + if err := dec.Object({{.ComponentPointerModifier}}value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s {{.HelperType}}) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object({{.ComponentPointerModifier}}s[i]) + } +} + + +func (s {{.HelperType}}) IsNil() bool { + return len(s) == 0 +} + + +`, + + resetStruct: ` +// Reset reset fields +func ({{.Receiver}}) Reset() { +{{.Reset}} +} +`, + + poolVar: `var {{.PoolName}} *sync.Pool`, + poolInit: ` {{.PoolName}} = &sync.Pool { + New: func()interface{} { + return &{{.Type}}{} + }, + }`, + embeddedStructInit: `if {{.Accessor}} == nil { + {{.Accessor}} = {{.Init}} + }`, +} + +func expandTemplate(namespace string, dictionary map[int]string, key int, data interface{}) (string, error) { + var id = fmt.Sprintf("%v_%v", namespace, key) + textTemplate, ok := dictionary[key] + if !ok { + return "", fmt.Errorf("failed to lookup template for %v.%v", namespace, key) + } + temlate, err := template.New(id).Parse(textTemplate) + if err != nil { + return "", fmt.Errorf("fiailed to parse template %v %v, due to %v", namespace, key, err) + } + writer := new(bytes.Buffer) + err = temlate.Execute(writer, data) + return writer.String(), err +} + +func expandFieldTemplate(key int, data interface{}) (string, error) { + return expandTemplate("fieldTemplate", fieldTemplate, key, data) +} + +func expandBlockTemplate(key int, data interface{}) (string, error) { + return expandTemplate("blockTemplate", blockTemplate, key, data) +} diff --git a/codegen/template_test.go b/codegen/template_test.go @@ -0,0 +1,19 @@ +package codegen + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_ExpandTemplate(t *testing.T) { + var dictionary = map[int]string{ + 1: `type {{.TypeName}} {{.SourceTypeName}}`, + } + expaded, err := expandTemplate("test", dictionary, 1, struct { + TypeName string + SourceTypeName string + }{"A", "B"}) + + assert.Nil(t, err) + assert.Equal(t, "type A B", expaded) +} diff --git a/codegen/test/annotated_struct/encoding.go b/codegen/test/annotated_struct/encoding.go @@ -0,0 +1,307 @@ +// Code generated by GoJayGen. DO NOT EDIT.\n\n +package annotated_struct + +import ( +"github.com/francoispqt/gojay" +"time" +) + + + + + + +type Ints []int + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Ints) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value int + if err := dec.Int(&value); err != nil { + return err + } + *a = append(*a, value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Ints) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Int(item) + } +} + +//IsNil checks if array is nil +func (a Ints) IsNil() bool { + return len(a) == 0 +} + + + +type Float32sPtr []*float32 + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Float32sPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value float32 + if err := dec.Float32(&value); err != nil { + return err + } + *a = append(*a, &value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Float32sPtr) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Float32(*item) + } +} + +//IsNil checks if array is nil +func (a Float32sPtr) IsNil() bool { + return len(a) == 0 +} + + +type SubMessagesPtr []*SubMessage + +func (s *SubMessagesPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = &SubMessage{} + if err := dec.Object(value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessagesPtr) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(s[i]) + } +} + + +func (s SubMessagesPtr) IsNil() bool { + return len(s) == 0 +} + + + + +type SubMessages []SubMessage + +func (s *SubMessages) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = SubMessage{} + if err := dec.Object(&value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessages) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(&s[i]) + } +} + + +func (s SubMessages) IsNil() bool { + return len(s) == 0 +} + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *SubMessage) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("id", m.Id) + enc.StringKey("description", m.Description) + enc.TimeKey("startDate", &m.StartTime, "2006-01-02 15:04:05") + if m.EndTime != nil { + enc.TimeKey("endDate", m.EndTime, "2006-01-02 15:04:05") + } +} + +//IsNil checks if instance is nil +func (m *SubMessage) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *SubMessage) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "id": + return dec.Int(&m.Id) + + case "description": + return dec.String(&m.Description) + + case "startDate": + var format = "2006-01-02 15:04:05" + var value = time.Time{} + err := dec.DecodeTime(&value, format) + return err + + case "endDate": + var format = "2006-01-02 15:04:05" + var value = &time.Time{} + err := dec.DecodeTime(value, format) + if err == nil { + m.EndTime = value + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *SubMessage) NKeys() int { return 4 } + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (p *Paylod) MarshalJSONObject(enc *gojay.Encoder) { + +} + +//IsNil checks if instance is nil +func (p *Paylod) IsNil() bool { + return p == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (p *Paylod) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (p *Paylod) NKeys() int { return 0 } + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("id", m.Id) + enc.StringKey("name", m.Name) + enc.Float64Key("price", m.Price) + var intsSlice = Ints(m.Ints) + enc.ArrayKey("ints",intsSlice) + var floatsSlice = Float32sPtr(m.Floats) + enc.ArrayKey("floats",floatsSlice) + enc.ObjectKey("subMessageX", m.SubMessageX) + var messagesXSlice = SubMessagesPtr(m.MessagesX) + enc.ArrayKey("messagesX", messagesXSlice) + enc.ObjectKey("SubMessageY", &m.SubMessageY) + var messagesYSlice = SubMessages(m.MessagesY) + enc.ArrayKey("MessagesY", messagesYSlice) + enc.BoolKey("enabled", *m.IsTrue) + var payloadSlice = gojay.EmbeddedJSON(m.Payload) + enc.AddEmbeddedJSONKey("data", &payloadSlice) +} + +//IsNil checks if instance is nil +func (m *Message) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "id": + return dec.Int(&m.Id) + + case "name": + return dec.String(&m.Name) + + case "price": + return dec.Float64(&m.Price) + + case "ints": + var aSlice = Ints{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Ints = []int(aSlice) + } + return err + + case "floats": + var aSlice = Float32sPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Floats = []*float32(aSlice) + } + return err + + case "subMessageX": + var value = &SubMessage{} + err := dec.Object(value) + if err == nil { + m.SubMessageX = value + } + + return err + + case "messagesX": + var aSlice = SubMessagesPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesX = []*SubMessage(aSlice) + } + return err + + case "SubMessageY": + err := dec.Object(&m.SubMessageY) + + return err + + case "MessagesY": + var aSlice = SubMessages{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesY = []SubMessage(aSlice) + } + return err + + case "enabled": + var value bool + err := dec.Bool(&value) + if err == nil { + m.IsTrue = &value + } + return err + + + case "data": + var value = gojay.EmbeddedJSON{} + err := dec.AddEmbeddedJSON(&value) + if err == nil && len(value) > 0 { + m.Payload = Paylod(value) + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *Message) NKeys() int { return 11 } + + + + + diff --git a/codegen/test/annotated_struct/encoding_test.go b/codegen/test/annotated_struct/encoding_test.go @@ -0,0 +1,216 @@ +package annotated_struct + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/francoispqt/gojay" + "github.com/viant/assertly" + "log" + "bytes" +) + +func TestMessage_Unmarshal(t *testing.T) { + + + + input := `{ + "id": 1022, + "name": "name acc", + "price": 13.3, + "ints": [ + 1, + 2, + 5 + ], + "floats": [ + 2.3, + 4.6, + 7.4 + ], + "subMessageX": { + "id": 102, + "description": "abcd" + }, + "messagesX": [ + { + "id": 2102, + "description": "abce" + } + ], + "SubMessageY": { + "id": 3102, + "description": "abcf" + }, + "MessagesY": [ + { + "id": 5102, + "description": "abcg" + }, + { + "id": 5106, + "description": "abcgg" + } + ], + "enabled": true, + "data": "123" +}` + + + expacted := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "\"123\"" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + if !assert.Nil(t, err) { + log.Fatal(err) + } + assertly.AssertValues(t, expacted, message) +} + + + + +func TestMessage_Marshal(t *testing.T) { + + input := `{ + "id": 1022, + "name": "name acc", + "price": 13.3, + "ints": [ + 1, + 2, + 5 + ], + "floats": [ + 2.3, + 4.6, + 7.4 + ], + "subMessageX": { + "id": 102, + "description": "abcd" + }, + "messagesX": [ + { + "id": 2102, + "description": "abce" + } + ], + "SubMessageY": { + "id": 3102, + "description": "abcf" + }, + "MessagesY": [ + { + "id": 5102, + "description": "abcg" + }, + { + "id": 5106, + "description": "abcgg" + } + ], + "enabled": true, + "data": "123" +}` + + + expacted := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "\"123\"" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + if !assert.Nil(t, err) { + log.Fatal(err) + } + assertly.AssertValues(t, expacted, message) + var writer = new(bytes.Buffer) + encoder := gojay.NewEncoder(writer) + err = encoder.Encode(message) + assert.Nil(t, err) + var JSON = writer.String() + assertly.AssertValues(t, input, JSON) + +} +\ No newline at end of file diff --git a/codegen/test/annotated_struct/message.go b/codegen/test/annotated_struct/message.go @@ -0,0 +1,19 @@ +package annotated_struct + + +type Paylod []byte + +type Message struct { + Id int `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` + Ints []int `json:"ints"` + Floats []*float32 `json:"floats"` + SubMessageX *SubMessage `json:"subMessageX"` + MessagesX []*SubMessage `json:"messagesX"` + SubMessageY SubMessage + MessagesY []SubMessage + IsTrue *bool `json:"enabled"` + Payload Paylod `json:"data"` + Ignore string `json:"-"` +} diff --git a/codegen/test/annotated_struct/sub_message.go b/codegen/test/annotated_struct/sub_message.go @@ -0,0 +1,10 @@ +package annotated_struct + +import "time" + +type SubMessage struct { + Id int `json:"id"` + Description string `json:"description"` + StartTime time.Time `json:"startDate" timeFormat:"yyyy-MM-dd HH:mm:ss"` + EndTime *time.Time `json:"endDate" timeLayout:"2006-01-02 15:04:05"` +} diff --git a/codegen/test/basic_struct/encoding.go b/codegen/test/basic_struct/encoding.go @@ -0,0 +1,280 @@ +// Code generated by GoJayGen. DO NOT EDIT.\n\n +package basic_struct + +import ( +"github.com/francoispqt/gojay" +"time" +) + + + + + + +type Ints []int + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Ints) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value int + if err := dec.Int(&value); err != nil { + return err + } + *a = append(*a, value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Ints) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Int(item) + } +} + +//IsNil checks if array is nil +func (a Ints) IsNil() bool { + return len(a) == 0 +} + + + +type Float32sPtr []*float32 + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Float32sPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value float32 + if err := dec.Float32(&value); err != nil { + return err + } + *a = append(*a, &value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Float32sPtr) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Float32(*item) + } +} + +//IsNil checks if array is nil +func (a Float32sPtr) IsNil() bool { + return len(a) == 0 +} + + +type SubMessagesPtr []*SubMessage + +func (s *SubMessagesPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = &SubMessage{} + if err := dec.Object(value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessagesPtr) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(s[i]) + } +} + + +func (s SubMessagesPtr) IsNil() bool { + return len(s) == 0 +} + + + + +type SubMessages []SubMessage + +func (s *SubMessages) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = SubMessage{} + if err := dec.Object(&value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessages) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(&s[i]) + } +} + + +func (s SubMessages) IsNil() bool { + return len(s) == 0 +} + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *SubMessage) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("Id", m.Id) + enc.StringKey("Description", m.Description) + enc.TimeKey("StartTime", &m.StartTime, time.RFC3339) + if m.EndTime != nil { + enc.TimeKey("EndTime", m.EndTime, time.RFC3339) + } +} + +//IsNil checks if instance is nil +func (m *SubMessage) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *SubMessage) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Id": + return dec.Int(&m.Id) + + case "Description": + return dec.String(&m.Description) + + case "StartTime": + var format = time.RFC3339 + var value = time.Time{} + err := dec.DecodeTime(&value, format) + return err + + case "EndTime": + var format = time.RFC3339 + var value = &time.Time{} + err := dec.DecodeTime(value, format) + if err == nil { + m.EndTime = value + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *SubMessage) NKeys() int { return 4 } + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("Id", m.Id) + enc.StringKey("Name", m.Name) + enc.Float64Key("Price", m.Price) + var intsSlice = Ints(m.Ints) + enc.ArrayKey("Ints",intsSlice) + var floatsSlice = Float32sPtr(m.Floats) + enc.ArrayKey("Floats",floatsSlice) + enc.ObjectKey("SubMessageX", m.SubMessageX) + var messagesXSlice = SubMessagesPtr(m.MessagesX) + enc.ArrayKey("MessagesX", messagesXSlice) + enc.ObjectKey("SubMessageY", &m.SubMessageY) + var messagesYSlice = SubMessages(m.MessagesY) + enc.ArrayKey("MessagesY", messagesYSlice) + enc.BoolKey("IsTrue", *m.IsTrue) + var payloadSlice = gojay.EmbeddedJSON(m.Payload) + enc.AddEmbeddedJSONKey("Payload", &payloadSlice) +} + +//IsNil checks if instance is nil +func (m *Message) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Id": + return dec.Int(&m.Id) + + case "Name": + return dec.String(&m.Name) + + case "Price": + return dec.Float64(&m.Price) + + case "Ints": + var aSlice = Ints{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Ints = []int(aSlice) + } + return err + + case "Floats": + var aSlice = Float32sPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Floats = []*float32(aSlice) + } + return err + + case "SubMessageX": + var value = &SubMessage{} + err := dec.Object(value) + if err == nil { + m.SubMessageX = value + } + + return err + + case "MessagesX": + var aSlice = SubMessagesPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesX = []*SubMessage(aSlice) + } + return err + + case "SubMessageY": + err := dec.Object(&m.SubMessageY) + + return err + + case "MessagesY": + var aSlice = SubMessages{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesY = []SubMessage(aSlice) + } + return err + + case "IsTrue": + var value bool + err := dec.Bool(&value) + if err == nil { + m.IsTrue = &value + } + return err + + + case "Payload": + var value = gojay.EmbeddedJSON{} + err := dec.AddEmbeddedJSON(&value) + if err == nil && len(value) > 0 { + m.Payload = []byte(value) + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *Message) NKeys() int { return 11 } + + + + + diff --git a/codegen/test/basic_struct/encoding_test.go b/codegen/test/basic_struct/encoding_test.go @@ -0,0 +1,122 @@ +package basic_struct + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/francoispqt/gojay" + "github.com/viant/assertly" + "bytes" +) + +func TestMessage_Unmarshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + assertly.AssertValues(t, input, message) +} + + + +func TestMessage_Marshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + + var writer = new(bytes.Buffer) + + encoder := gojay.NewEncoder(writer) + err = encoder.Encode(message) + assert.Nil(t, err) + var JSON = writer.String() + assertly.AssertValues(t, input, JSON) +} diff --git a/codegen/test/basic_struct/message.go b/codegen/test/basic_struct/message.go @@ -0,0 +1,18 @@ +package basic_struct + + + + +type Message struct { + Id int + Name string + Price float64 + Ints []int + Floats []*float32 + SubMessageX *SubMessage + MessagesX []*SubMessage + SubMessageY SubMessage + MessagesY []SubMessage + IsTrue *bool + Payload []byte +} diff --git a/codegen/test/basic_struct/sub_message.go b/codegen/test/basic_struct/sub_message.go @@ -0,0 +1,10 @@ +package basic_struct + +import "time" + +type SubMessage struct { + Id int + Description string + StartTime time.Time + EndTime *time.Time +} diff --git a/codegen/test/embedded_struct/encoding.go b/codegen/test/embedded_struct/encoding.go @@ -0,0 +1,336 @@ +// Code generated by GoJayGen. DO NOT EDIT.\n\n +package embedded_struct + +import ( +"github.com/francoispqt/gojay" +"time" +) + + + + + +type SubMessagesPtr []*SubMessage + +func (s *SubMessagesPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = &SubMessage{} + if err := dec.Object(value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessagesPtr) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(s[i]) + } +} + + +func (s SubMessagesPtr) IsNil() bool { + return len(s) == 0 +} + + + + +type SubMessages []SubMessage + +func (s *SubMessages) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = SubMessage{} + if err := dec.Object(&value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessages) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(&s[i]) + } +} + + +func (s SubMessages) IsNil() bool { + return len(s) == 0 +} + + + + + +type Ints []int + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Ints) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value int + if err := dec.Int(&value); err != nil { + return err + } + *a = append(*a, value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Ints) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Int(item) + } +} + +//IsNil checks if array is nil +func (a Ints) IsNil() bool { + return len(a) == 0 +} + + + +type Float32sPtr []*float32 + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Float32sPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value float32 + if err := dec.Float32(&value); err != nil { + return err + } + *a = append(*a, &value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Float32sPtr) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Float32(*item) + } +} + +//IsNil checks if array is nil +func (a Float32sPtr) IsNil() bool { + return len(a) == 0 +} + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (i *BaseId) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("Id", i.Id) + enc.StringKey("Name", i.Name) +} + +//IsNil checks if instance is nil +func (i *BaseId) IsNil() bool { + return i == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (i *BaseId) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Id": + return dec.Int(&i.Id) + + case "Name": + return dec.String(&i.Name) + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (i *BaseId) NKeys() int { return 2 } + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *SubMessage) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("Description", m.Description) + enc.TimeKey("StartTime", &m.StartTime, time.RFC3339) + if m.EndTime != nil { + enc.TimeKey("EndTime", m.EndTime, time.RFC3339) + } +} + +//IsNil checks if instance is nil +func (m *SubMessage) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *SubMessage) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Description": + return dec.String(&m.Description) + + case "StartTime": + var format = time.RFC3339 + var value = time.Time{} + err := dec.DecodeTime(&value, format) + return err + + case "EndTime": + var format = time.RFC3339 + var value = &time.Time{} + err := dec.DecodeTime(value, format) + if err == nil { + m.EndTime = value + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *SubMessage) NKeys() int { return 3 } + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { + if m.BaseId != nil { + enc.IntKey("Id", m.Id) + enc.StringKey("Name", m.Name) + } + enc.StringKey("Description", m.Description) + enc.TimeKey("StartTime", &m.StartTime, time.RFC3339) + if m.EndTime != nil { + enc.TimeKey("EndTime", m.EndTime, time.RFC3339) + } + enc.Float64Key("Price", m.Price) + var intsSlice = Ints(m.Ints) + enc.ArrayKey("Ints",intsSlice) + var floatsSlice = Float32sPtr(m.Floats) + enc.ArrayKey("Floats",floatsSlice) + enc.ObjectKey("SubMessageX", m.SubMessageX) + var messagesXSlice = SubMessagesPtr(m.MessagesX) + enc.ArrayKey("MessagesX", messagesXSlice) + enc.ObjectKey("SubMessageY", &m.SubMessageY) + var messagesYSlice = SubMessages(m.MessagesY) + enc.ArrayKey("MessagesY", messagesYSlice) + enc.BoolKey("IsTrue", *m.IsTrue) + var payloadSlice = gojay.EmbeddedJSON(m.Payload) + enc.AddEmbeddedJSONKey("Payload", &payloadSlice) +} + +//IsNil checks if instance is nil +func (m *Message) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { +if m.BaseId == nil { + m.BaseId = &BaseId{} + } + switch k { + case "Id": + return dec.Int(&m.Id) + + case "Name": + return dec.String(&m.Name) + + case "Description": + return dec.String(&m.Description) + + case "StartTime": + var format = time.RFC3339 + var value = time.Time{} + err := dec.DecodeTime(&value, format) + return err + + case "EndTime": + var format = time.RFC3339 + var value = &time.Time{} + err := dec.DecodeTime(value, format) + if err == nil { + m.EndTime = value + } + return err + + case "Price": + return dec.Float64(&m.Price) + + case "Ints": + var aSlice = Ints{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Ints = []int(aSlice) + } + return err + + case "Floats": + var aSlice = Float32sPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Floats = []*float32(aSlice) + } + return err + + case "SubMessageX": + var value = &SubMessage{} + err := dec.Object(value) + if err == nil { + m.SubMessageX = value + } + + return err + + case "MessagesX": + var aSlice = SubMessagesPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesX = []*SubMessage(aSlice) + } + return err + + case "SubMessageY": + err := dec.Object(&m.SubMessageY) + + return err + + case "MessagesY": + var aSlice = SubMessages{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesY = []SubMessage(aSlice) + } + return err + + case "IsTrue": + var value bool + err := dec.Bool(&value) + if err == nil { + m.IsTrue = &value + } + return err + + + case "Payload": + var value = gojay.EmbeddedJSON{} + err := dec.AddEmbeddedJSON(&value) + if err == nil && len(value) > 0 { + m.Payload = []byte(value) + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *Message) NKeys() int { return 14 } + + + + + diff --git a/codegen/test/embedded_struct/encoding_test.go b/codegen/test/embedded_struct/encoding_test.go @@ -0,0 +1,108 @@ +package embedded_struct + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/francoispqt/gojay" + "github.com/viant/assertly" + "bytes" +) + +func TestMessage_Unmarshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Description": "abcd", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "MessagesX": [ + { + "Description": "abce" + } + ], + "SubMessageY": { + "Description": "abcf" + }, + "MessagesY": [ + { + "Description": "abcg" + }, + { + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + assertly.AssertValues(t, input, message) +} + + + +func TestMessage_Marshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Description": "abcd", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "MessagesX": [ + { + "Description": "abce" + } + ], + "SubMessageY": { + "Description": "abcf" + }, + "MessagesY": [ + { + "Description": "abcg" + }, + { + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var err error + var data = []byte(input) + message := &Message{} + err = gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + assertly.AssertValues(t, input, message) + var writer = new(bytes.Buffer) + + encoder := gojay.NewEncoder(writer) + err = encoder.Encode(message) + assert.Nil(t, err) + var JSON = writer.String() + assertly.AssertValues(t, input, JSON) +} +\ No newline at end of file diff --git a/codegen/test/embedded_struct/message.go b/codegen/test/embedded_struct/message.go @@ -0,0 +1,22 @@ +package embedded_struct + + +type BaseId struct { + Id int + Name string + +} + +type Message struct { + *BaseId + SubMessage + Price float64 + Ints []int + Floats []*float32 + SubMessageX *SubMessage + MessagesX []*SubMessage + SubMessageY SubMessage + MessagesY []SubMessage + IsTrue *bool + Payload []byte +} diff --git a/codegen/test/embedded_struct/sub_message.go b/codegen/test/embedded_struct/sub_message.go @@ -0,0 +1,9 @@ +package embedded_struct + +import "time" + +type SubMessage struct { + Description string + StartTime time.Time + EndTime *time.Time +} diff --git a/codegen/test/pooled_struct/encoding.go b/codegen/test/pooled_struct/encoding.go @@ -0,0 +1,326 @@ +// Code generated by GoJayGen. DO NOT EDIT.\n\n +package pooled_struct + +import ( +"sync" +"time" +"github.com/francoispqt/gojay" +) + + +func init() { + MessagePool = &sync.Pool { + New: func()interface{} { + return &Message{} + }, + } + SubMessagePool = &sync.Pool { + New: func()interface{} { + return &SubMessage{} + }, + } +} + + +var MessagePool *sync.Pool +var SubMessagePool *sync.Pool + + + +type Ints []int + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Ints) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value int + if err := dec.Int(&value); err != nil { + return err + } + *a = append(*a, value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Ints) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Int(item) + } +} + +//IsNil checks if array is nil +func (a Ints) IsNil() bool { + return len(a) == 0 +} + + + +type Float32sPtr []*float32 + +//UnmarshalJSONArray decodes JSON array elements into slice +func (a *Float32sPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value float32 + if err := dec.Float32(&value); err != nil { + return err + } + *a = append(*a, &value) + return nil +} + +//MarshalJSONArray encodes arrays into JSON +func (a Float32sPtr) MarshalJSONArray(enc *gojay.Encoder) { + for _, item := range a { + enc.Float32(*item) + } +} + +//IsNil checks if array is nil +func (a Float32sPtr) IsNil() bool { + return len(a) == 0 +} + + +type SubMessagesPtr []*SubMessage + +func (s *SubMessagesPtr) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = &SubMessage{} + if err := dec.Object(value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessagesPtr) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(s[i]) + } +} + + +func (s SubMessagesPtr) IsNil() bool { + return len(s) == 0 +} + + + + +type SubMessages []SubMessage + +func (s *SubMessages) UnmarshalJSONArray(dec *gojay.Decoder) error { + var value = SubMessage{} + if err := dec.Object(&value); err != nil { + return err + } + *s = append(*s, value) + return nil +} + +func (s SubMessages) MarshalJSONArray(enc *gojay.Encoder) { + for i := range s { + enc.Object(&s[i]) + } +} + + +func (s SubMessages) IsNil() bool { + return len(s) == 0 +} + + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *SubMessage) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("Id", m.Id) + enc.StringKey("Description", m.Description) + enc.TimeKey("StartTime", &m.StartTime, time.RFC3339) + if m.EndTime != nil { + enc.TimeKey("EndTime", m.EndTime, time.RFC3339) + } +} + +//IsNil checks if instance is nil +func (m *SubMessage) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *SubMessage) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Id": + return dec.Int(&m.Id) + + case "Description": + return dec.String(&m.Description) + + case "StartTime": + var format = time.RFC3339 + var value = time.Time{} + err := dec.DecodeTime(&value, format) + return err + + case "EndTime": + var format = time.RFC3339 + var value = &time.Time{} + err := dec.DecodeTime(value, format) + if err == nil { + m.EndTime = value + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *SubMessage) NKeys() int { return 4 } + + +// Reset reset fields +func (m *SubMessage) Reset() { + m.Id = 0 + m.Description = "" + m.EndTime = nil +} + + + + + +//MarshalJSONObject implements MarshalerJSONObject +func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("Id", m.Id) + enc.StringKey("Name", m.Name) + enc.Float64Key("Price", m.Price) + var intsSlice = Ints(m.Ints) + enc.ArrayKey("Ints",intsSlice) + var floatsSlice = Float32sPtr(m.Floats) + enc.ArrayKey("Floats",floatsSlice) + enc.ObjectKey("SubMessageX", m.SubMessageX) + var messagesXSlice = SubMessagesPtr(m.MessagesX) + enc.ArrayKey("MessagesX", messagesXSlice) + enc.ObjectKey("SubMessageY", &m.SubMessageY) + var messagesYSlice = SubMessages(m.MessagesY) + enc.ArrayKey("MessagesY", messagesYSlice) + enc.BoolKey("IsTrue", *m.IsTrue) + var payloadSlice = gojay.EmbeddedJSON(m.Payload) + enc.AddEmbeddedJSONKey("Payload", &payloadSlice) +} + +//IsNil checks if instance is nil +func (m *Message) IsNil() bool { + return m == nil +} + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + + switch k { + case "Id": + return dec.Int(&m.Id) + + case "Name": + return dec.String(&m.Name) + + case "Price": + return dec.Float64(&m.Price) + + case "Ints": + var aSlice = Ints{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Ints = []int(aSlice) + } + return err + + case "Floats": + var aSlice = Float32sPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.Floats = []*float32(aSlice) + } + return err + + case "SubMessageX": + var value = SubMessagePool.Get().(*SubMessage) + err := dec.Object(value) + if err == nil { + m.SubMessageX = value + } + + return err + + case "MessagesX": + var aSlice = SubMessagesPtr{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesX = []*SubMessage(aSlice) + } + return err + + case "SubMessageY": + err := dec.Object(&m.SubMessageY) + + return err + + case "MessagesY": + var aSlice = SubMessages{} + err := dec.Array(&aSlice) + if err == nil && len(aSlice) > 0 { + m.MessagesY = []SubMessage(aSlice) + } + return err + + case "IsTrue": + var value bool + err := dec.Bool(&value) + if err == nil { + m.IsTrue = &value + } + return err + + + case "Payload": + var value = gojay.EmbeddedJSON{} + err := dec.AddEmbeddedJSON(&value) + if err == nil && len(value) > 0 { + m.Payload = []byte(value) + } + return err + + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (m *Message) NKeys() int { return 11 } + + +// Reset reset fields +func (m *Message) Reset() { + m.Id = 0 + m.Name = "" + m.Price = 0.0 + m.Ints = nil + m.Floats = nil + SubMessagePool.Put(m.SubMessageX) + m.SubMessageX = nil + for i := range m.MessagesX { + m.MessagesX[i].Reset() + SubMessagePool.Put(m.MessagesX[i]) + } + m.MessagesX = nil + for i := range m.MessagesY { + m.MessagesY[i].Reset() + SubMessagePool.Put(&m.MessagesY[i]) + } + m.MessagesY = nil + m.IsTrue = nil + m.Payload = nil +} + + + + diff --git a/codegen/test/pooled_struct/encoding_test.go b/codegen/test/pooled_struct/encoding_test.go @@ -0,0 +1,124 @@ +package pooled_struct + +import ( + "testing" + "github.com/francoispqt/gojay" + "github.com/stretchr/testify/assert" + "github.com/viant/assertly" + "bytes" +) + +func TestMessage_Unmarshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var data = []byte(input) + message := MessagePool.Get().(*Message) + err :=gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + message.Reset() + MessagePool.Put(message) + +} + + +func TestMessage_Marshal(t *testing.T) { + + input := `{ + "Id": 1022, + "Name": "name acc", + "Price": 13.3, + "Ints": [ + 1, + 2, + 5 + ], + "Floats": [ + 2.3, + 4.6, + 7.4 + ], + "SubMessageX": { + "Id": 102, + "Description": "abcd" + }, + "MessagesX": [ + { + "Id": 2102, + "Description": "abce" + } + ], + "SubMessageY": { + "Id": 3102, + "Description": "abcf" + }, + "MessagesY": [ + { + "Id": 5102, + "Description": "abcg" + }, + { + "Id": 5106, + "Description": "abcgg" + } + ], + "IsTrue": true, + "Payload": "" +}` + + var data = []byte(input) + message := MessagePool.Get().(*Message) + err :=gojay.UnmarshalJSONObject(data, message) + assert.Nil(t, err) + defer func() { + message.Reset() + MessagePool.Put(message) + + }() + var writer = new(bytes.Buffer) + encoder := gojay.NewEncoder(writer) + err = encoder.Encode(message) + assert.Nil(t, err) + var JSON = writer.String() + assertly.AssertValues(t, input, JSON) +} +\ No newline at end of file diff --git a/codegen/test/pooled_struct/message.go b/codegen/test/pooled_struct/message.go @@ -0,0 +1,18 @@ +package pooled_struct + + + + +type Message struct { + Id int + Name string + Price float64 + Ints []int + Floats []*float32 + SubMessageX *SubMessage + MessagesX []*SubMessage + SubMessageY SubMessage + MessagesY []SubMessage + IsTrue *bool + Payload []byte +} diff --git a/codegen/test/pooled_struct/sub_message.go b/codegen/test/pooled_struct/sub_message.go @@ -0,0 +1,10 @@ +package pooled_struct + +import "time" + +type SubMessage struct { + Id int + Description string + StartTime time.Time + EndTime *time.Time +} diff --git a/gojaygen/gojaygen b/gojaygen/gojaygen Binary files differ. diff --git a/gojaygen/gojaygen.go b/gojaygen/gojaygen.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + "github.com/adrianwit/gojay/codegen" + "log" +) + +var dst = flag.String("o", "", "destination file to output generated implementations") +var src = flag.String("s", "", "source dir or file (absolute or relative path)") +var types = flag.String("t", "", "types to generate") +var annotation = flag.String("a", "json", "annotation tagg") +var poolObjects = flag.String("p", "", "generate code to reuse objects") + +func main() { + flag.Parse() + options := codegen.NewOptionsWithFlagSet(flag.CommandLine) + gen := &codegen.Generator{} + if err := gen.Generate(options); err != nil { + log.Fatal(err) + } +}