Skip to content

Commit

Permalink
feat: add support for ,string annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Mar 6, 2024
1 parent 588e1bd commit fb8cd16
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 5 deletions.
9 changes: 9 additions & 0 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Generator struct {
packageComment string
packageName string
skipUnparsableProperties bool
stringAnnotations bool
structTagNames []string
typeComment string
typeName string
Expand Down Expand Up @@ -127,6 +128,13 @@ func WithSkipUnparsableProperties(skipUnparsableProperties bool) GeneratorOption
}
}

// WithStringAnnotations sets whether ",string" annotations should be used.
func WithStringAnnotations(stringAnnotations bool) GeneratorOption {
return func(g *Generator) {
g.stringAnnotations = stringAnnotations
}
}

// WithStructTagName sets the struct tag name.
func WithStructTagName(structTagName string) GeneratorOption {
return func(g *Generator) {
Expand Down Expand Up @@ -222,6 +230,7 @@ func (g *Generator) Generate() ([]byte, error) {
intType: g.intType,
omitEmptyOption: g.omitEmptyOption,
skipUnparsableProperties: g.skipUnparsableProperties,
stringAnnotations: g.stringAnnotations,
structTagNames: g.structTagNames,
useJSONNumber: g.useJSONNumber,
})
Expand Down
200 changes: 200 additions & 0 deletions generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,205 @@ func TestGoType(t *testing.T) {
},
expectedGoTypeStr: "struct {\nKey int64 `json:\"key\"`\n}",
},
{
name: "bool_strings",
values: []any{
map[string]any{
"key": "true",
},
map[string]any{
"key": "false",
},
},
expectedValue: &value{
observations: 2,
objects: 2,
objectProperties: map[string]*value{
"key": {
observations: 2,
boolStrings: 2,
strings: 2,
},
},
allObjectProperties: &value{
observations: 2,
boolStrings: 2,
strings: 2,
},
},
generatorOptions: []GeneratorOption{
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey bool `json:\"key,string\"`\n}",
},
{
name: "bool_strings_with_empty",
values: []any{
map[string]any{
"key": "true",
},
map[string]any{
"key": "false",
},
map[string]any{
"key": "",
},
},
expectedValue: &value{
observations: 3,
objects: 3,
objectProperties: map[string]*value{
"key": {
observations: 3,
empties: 1,
boolStrings: 2,
strings: 3,
},
},
allObjectProperties: &value{
observations: 3,
empties: 1,
boolStrings: 2,
strings: 3,
},
},
generatorOptions: []GeneratorOption{
WithOmitEmpty(OmitEmptyAlways),
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey string `json:\"key,omitempty\"`\n}",
},
{
name: "bool_strings_with_missing",
values: []any{
map[string]any{
"key": "true",
},
map[string]any{
"key": "false",
},
map[string]any{},
},
expectedValue: &value{
observations: 3,
empties: 1,
objects: 3,
objectProperties: map[string]*value{
"key": {
observations: 2,
boolStrings: 2,
strings: 2,
},
},
allObjectProperties: &value{
observations: 2,
boolStrings: 2,
strings: 2,
},
},
generatorOptions: []GeneratorOption{
WithOmitEmpty(OmitEmptyAlways),
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey bool `json:\"key,omitempty,string\"`\n}",
},
{
name: "int_strings",
values: []any{
map[string]any{
"key": "1",
},
map[string]any{
"key": "2",
},
},
expectedValue: &value{
observations: 2,
objects: 2,
objectProperties: map[string]*value{
"key": {
observations: 2,
float64Strings: 2,
intStrings: 2,
strings: 2,
},
},
allObjectProperties: &value{
observations: 2,
float64Strings: 2,
intStrings: 2,
strings: 2,
},
},
generatorOptions: []GeneratorOption{
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey int `json:\"key,string\"`\n}",
},
{
name: "float64_strings",
values: []any{
map[string]any{
"key": "1.1",
},
map[string]any{
"key": "2.2",
},
},
expectedValue: &value{
observations: 2,
objects: 2,
objectProperties: map[string]*value{
"key": {
observations: 2,
float64Strings: 2,
strings: 2,
},
},
allObjectProperties: &value{
observations: 2,
float64Strings: 2,
strings: 2,
},
},
generatorOptions: []GeneratorOption{
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey float64 `json:\"key,string\"`\n}",
},
{
name: "int_and_float64_strings",
values: []any{
map[string]any{
"key": "1",
},
map[string]any{
"key": "2.2",
},
},
expectedValue: &value{
observations: 2,
objects: 2,
objectProperties: map[string]*value{
"key": {
observations: 2,
float64Strings: 2,
intStrings: 1,
strings: 2,
},
},
allObjectProperties: &value{
observations: 2,
float64Strings: 2,
intStrings: 1,
strings: 2,
},
},
generatorOptions: []GeneratorOption{
WithStringAnnotations(true),
},
expectedGoTypeStr: "struct {\nKey float64 `json:\"key,string\"`\n}",
},
{
name: "object_unparsable_properties_skip",
values: []any{
Expand Down Expand Up @@ -717,6 +916,7 @@ func TestGoType(t *testing.T) {
intType: generator.intType,
omitEmptyOption: generator.omitEmptyOption,
skipUnparsableProperties: generator.skipUnparsableProperties,
stringAnnotations: generator.stringAnnotations,
structTagNames: generator.structTagNames,
useJSONNumber: generator.useJSONNumber,
}
Expand Down
47 changes: 42 additions & 5 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ type value struct {
empties int
arrays int
bools int
boolStrings int
float64s int
float64Strings int
ints int
intStrings int
nulls int
objects int
strings int
Expand All @@ -33,13 +36,15 @@ type generateOptions struct {
intType string
omitEmptyOption OmitEmptyOption
skipUnparsableProperties bool
stringAnnotations bool
structTagNames []string
useJSONNumber bool
}

type goType struct {
typeStr string
omitEmpty bool
typeStr string
omitEmpty bool
stringAnnotation bool
}

// observe merges a into v.
Expand Down Expand Up @@ -93,6 +98,14 @@ func (v *value) observe(a any) *value {
if a == "" {
v.empties++
}
if err := json.Unmarshal([]byte(a), new(bool)); err == nil {
v.boolStrings++
} else if err := json.Unmarshal([]byte(a), new(int)); err == nil {
v.float64Strings++
v.intStrings++
} else if err := json.Unmarshal([]byte(a), new(float64)); err == nil {
v.float64Strings++
}
if v.times == v.strings {
if _, err := time.Parse(time.RFC3339Nano, a); err == nil {
v.times++
Expand Down Expand Up @@ -258,6 +271,9 @@ func (v *value) goType(observations int, options *generateOptions) goType {
if omitEmpty {
structTagOptions = append(structTagOptions, "omitempty")
}
if goType.stringAnnotation {
structTagOptions = append(structTagOptions, "string")
}
for _, structTagName := range options.structTagNames {
tag := &structtag.Tag{
Key: structTagName,
Expand Down Expand Up @@ -300,9 +316,30 @@ func (v *value) goType(observations int, options *generateOptions) goType {
omitEmpty: v.times < observations,
}
case distinctTypes == 1 && v.strings > 0:
return goType{
typeStr: "string",
omitEmpty: v.strings < observations && v.empties == 0,
switch {
case options.stringAnnotations && v.strings == v.boolStrings:
return goType{
typeStr: "bool",
stringAnnotation: true,
omitEmpty: v.boolStrings < v.observations,
}
case options.stringAnnotations && v.strings == v.intStrings:
return goType{
typeStr: options.intType,
stringAnnotation: true,
omitEmpty: v.intStrings < v.strings,
}
case options.stringAnnotations && v.strings == v.float64Strings:
return goType{
typeStr: "float64",
stringAnnotation: true,
omitEmpty: v.float64Strings < v.strings,
}
default:
return goType{
typeStr: "string",
omitEmpty: v.strings < observations && v.empties == 0,
}
}
case distinctTypes == 2 && v.strings > 0 && v.nulls > 0 && v.times == v.strings:
options.imports["time"] = struct{}{}
Expand Down

0 comments on commit fb8cd16

Please sign in to comment.